diff --git a/.gitignore b/.gitignore index 3d474a48..04d58954 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ OrangeDesignSystemDemo/DerivedData/ # Produced by Swift Package Manager .swiftpm/xcode/xcuserdata/ +.swiftpm/xcode/package.xcworkspace/ # Produced by Fastlane **/fastlane/*.env diff --git a/CHANGELOG.md b/CHANGELOG.md index 50724b59..71bffeb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). + +## [0.14.0](https://github.com/Orange-OpenSource/ods-ios/compare/0.14.0...0.13.1) - 2023-10-09 + +- [SDK] Update CardVerticalImageFirst api to use SwiftUI elements [#481](https://github.com/Orange-OpenSource/ods-ios/issues/481)) +- [SDK] Update CardVerticalHeaderFirst api to use SwiftUI elements [#479](https://github.com/Orange-OpenSource/ods-ios/issues/479)) +- [SDK] Update CardHorizontal api to use SwiftUI elements [#477](https://github.com/Orange-OpenSource/ods-ios/issues/477)) +- [SDK] Update Button api to use SwiftUI elements and use a buttonStyle [#471](https://github.com/Orange-OpenSource/ods-ios/issues/471)) +- [SDK] Update banner api to use SwiftUI elements [#473](https://github.com/Orange-OpenSource/ods-ios/issues/473)) +- [SDK] Accessibility Voice over - Application name is a header in the about screen (Bug [#468](https://github.com/Orange-OpenSource/ods-ios/issues/468)) +- [DemoApp/SDK] Update the button emphasis scale naming ([#464](https://github.com/Orange-OpenSource/ods-ios/issues/464)) +- [DemoApp] Update configuration to display by default buttons in variable width and without icon ([#459](https://github.com/Orange-OpenSource/ods-ios/issues/459)) +- [DemoApp/SDK] Icon Button Disables not setup ([#426](https://github.com/Orange-OpenSource/ods-ios/issues/426)) +- [Doc] Add documentation for release process ([#451](https://github.com/Orange-OpenSource/ods-ios/issues/451)) +- [DemoApp/SDK] About module: Review The ShareTheApp content because url is presented twice ([#435](https://github.com/Orange-OpenSource/ods-ios/issues/435)) + ## [0.13.1](https://github.com/Orange-OpenSource/ods-ios/compare/0.13.1...0.12.0) - 2023-09-13 - [DemoApp] Update AppNews for release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..7d3cc9a7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,124 @@ +# Contributing to Orange Design System iOS + +Looking to contribute something to Orange Design System iOS? **Here's how you can help.** + +Please take a moment to review this document in order to make the contribution process easy for everyone involved. + +Following these guidelines helps to communicate that you respect the time of the developers managing and developing this Open Source project. In return, they should reciprocate that respect in addressing your issue or assessing patches and features. + +## Using the Issue Tracker + +The [issue tracker](https://github.com/Orange-OpenSource/ods-ios/issues) is the preferred channel for [bug reports](#bug-reports), [feature requests](#feature-requests) and [submitting pull requests](#pull-requests), but please respect the following restrictions: + +- Please **do not** use the issue tracker for personal support requests. [GitHub Discussions](https://github.com/Orange-OpenSource/ods-ios/discussions/categories/q-a) or our internal Orange communication tools are better places to get help. + +- Please **do not** derail or troll issues. Keep the discussion on topic and respect the opinions of others. + +- Please **do not** post comments consisting solely of "+1" or ":thumbsup:". Use [GitHub's "reactions" feature](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) instead. We reserve the right to delete comments which violate this rule. + +## Issues and Labels + +Our bug tracker utilizes several labels to help organize and identify issues. Here's what they represent and how we use them: + +- `feature` - Issues asking for a new feature to be added, or an existing one to be extended or modified. New features require a minor version bump (e.g., `v1.0.0` to `v1.1.0`). +- `help wanted` - Issues we need or would love help from the community to resolve. + +For a complete look at our labels, see the [project labels page](https://github.com/Orange-OpenSource/ods-ios/labels). + +## Bug Reports + +A bug is a _demonstrable problem_ that is caused by the code in the repository. Good bug reports are extremely helpful, so thanks! + +Guidelines for bug reports: + +1. **Use the GitHub issue search** — check if the issue has already been reported. + +2. **Check if the issue has been fixed** — try to reproduce it using the latest `main` in the repository. + +3. **Isolate the problem** — ideally create a reduced reproducible test case. + +A good bug report shouldn't leave others needing to chase you up for more information. Please try to be as detailed as possible in your report. What is your environment? What steps will reproduce the issue? What device(s) and OS experience the problem? Do other devices show the bug differently? What would you expect to be the outcome? All these details will help people to fix any potential bugs. + +Example: + +> Short and descriptive example bug report title +> +> A summary of the issue and the browser/OS environment in which it occurs. If +> suitable, include the steps required to reproduce the bug. +> +> 1. This is the first step +> 2. This is the second step +> 3. Further steps, etc. +> +> `` - a link to the reduced test case +> +> Any other information you want to share that is relevant to the issue being +> reported. This might include the lines of code that you have identified as +> causing the bug, and potential solutions (and your opinions on their +> merits). + +## Feature Requests + +Feature requests are welcome. But take a moment to find out whether your idea fits with the scope and aims of the project. It's up to _you_ to make a strong case to convince the project's developers of the merits of this feature. Please provide as much detail and context as possible. + +## Pull requests + +Good pull requests—patches, improvements, new features—are a fantastic help. They should remain focused in scope and avoid containing unrelated commits. + +**Please ask first** before embarking on any **significant** pull request (e.g. implementing features, refactoring code, porting to a different language), otherwise you risk spending a lot of time working on something that the project's developers might not want to merge into the project. For trivial things, or things that don't require a lot of your time, you can go ahead and make a PR. + +Please adhere to the [coding guidelines](#code-guidelines) used throughout the project (indentation, accurate comments, etc.) and any other requirements (such as test coverage). + +Adhering to the following process is the best way to get your work included in the project: + +1. [Fork](https://help.github.com/articles/fork-a-repo/) the project, clone your fork, and configure the remotes: + + ```bash + # Clone your fork of the repo into the current directory + git clone https://github.com//ods-ios.git + # Navigate to the newly cloned directory + cd ods-ios + # Assign the original repo to a remote called "upstream" + git remote add upstream https://github.com/Orange-OpenSource/ods-ios.git + ``` + +2. If you cloned a while ago, get the latest changes from upstream: + + ```bash + git checkout main + git pull upstream main + ``` + +3. Create a new topic branch (off the main project development branch) to contain your feature, change, or fix: + + ```bash + git checkout -b + ``` + +4. Commit your changes in logical chunks. Use Git's [interactive rebase](https://help.github.com/articles/about-git-rebase/) feature to tidy up your commits before making them public. + +5. Locally merge (or rebase) the upstream development branch into your topic branch: + + ```bash + git pull [--rebase] upstream main + ``` + +6. Push your topic branch up to your fork: + + ```bash + git push origin + ``` + +7. [Open a Pull Request](https://help.github.com/articles/about-pull-requests/) with a clear title and description against the `main` branch. + +**IMPORTANT**: By submitting a patch, you agree to allow the project owners to license your work under the terms of the [MIT License](LICENSE). + +## Code Guidelines + +### Checking Coding Style + +Format your code before committing to ensure your changes follow our coding standards. + +## License + +By contributing your code, you agree to license your contribution under the [MIT License](LICENSE). diff --git a/DEVELOP.md b/DEVELOP.md new file mode 100644 index 00000000..891573ad --- /dev/null +++ b/DEVELOP.md @@ -0,0 +1,24 @@ +# Developer guide + +## Build OrangeDesignSystemDemo + +To build the demo application follow those steps. + +1. 'cd OrangeDesignSystemDemo' +2. `pod install` +3. Open OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcworkspace +4. Select OrangeDesignSystemDemo Scheme +5. Build and run the Application on your device ou simulator + + +## Documentation + +Execute the commands below to generate and run the documentation: + +1. `cd docs` +2. `bundle install` +3. `bundle exec jekyll serve --trace --watch --force_polling --livereload --livereload-port 4001` + +If you encounter errors during installation and your platform is not listed in the `PLATFORMS` section of `Gemfile.lock`, you can optionally run `bundle platform` to retrieve your platform, then `bundle lock --add-platform ` to install specific dependencies for your platform. + +Finally, open your browser and go to http://127.0.0.1:4000/ods-ios/ diff --git a/InnovationCupTheme/Sources/InnovationCupTheme/InnovationCupTheme.swift b/InnovationCupTheme/Sources/InnovationCupTheme/InnovationCupTheme.swift index 2034578c..96a73bd3 100644 --- a/InnovationCupTheme/Sources/InnovationCupTheme/InnovationCupTheme.swift +++ b/InnovationCupTheme/Sources/InnovationCupTheme/InnovationCupTheme.swift @@ -70,7 +70,7 @@ public struct InnovationCupThemeFactory { theme.componentColors.toolBarItem = .white // Buttons - theme.componentColors.highestEmphasisText = .white + theme.componentColors.highEmphasisText = .white theme.componentColors.functionalNegative = InnovationCupThemeColors.functionalNegative.colorDecription.color theme.componentColors.functionalPositive = InnovationCupThemeColors.functionalPositive.colorDecription.color diff --git a/OrangeDesignSystem/README.md b/OrangeDesignSystem/README.md index bb533d72..afddb4b6 100644 --- a/OrangeDesignSystem/README.md +++ b/OrangeDesignSystem/README.md @@ -1,3 +1,14 @@ -# ods-ios-swiftUI +

OrangeDesignSystem iOS Library

+ +

+ Orange Design System iOS provides Orange iOS components to developers and a demo application. +
+ Visit Orange Design System iOS +
+
+ Report bug + · + Request feature +

+ -A description of this package. diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Banner/ODSBanner.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Banner/ODSBanner.swift index 37742eeb..0c5f09bc 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Banner/ODSBanner.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Banner/ODSBanner.swift @@ -28,74 +28,69 @@ import SwiftUI /// public struct ODSBanner: View { - public enum OneButtonPosition { - case trailing - case bottom - } - // ======================= // MARK: Stored Properties // ======================= - private let text: LocalizedStringKey - private let image: Image? - private let buttonOptions: ButtonsOptions? + private let text: Text + private let imageSource: ODSImage.Source? + private let firstButton: (() -> Button)? + private let secondButton: (() -> Button)? // ================== // MARK: Initializers // ================== - /// Initialize the simpliest banner with no buttons. + /// Initialize the banner with two buttons added under the text. /// /// - Parameters: - /// - text: Text displayed in the button. - /// - image: Painter of the icon. If `nil`, no icon will be displayed. - public init(text: LocalizedStringKey, image: Image? = nil) { + /// - text: Text displayed in the banner. + /// - imageSource: Image displayed before the text in a circle area. If `nil`, no image will be displayed. + /// - firstButton: First (leading) button (text only) added under the text. + /// - secondButton: Second (trailing) button (text only) added under the text. + /// + /// - Remarks: The default lowest emphasis is automatically applied on buttons. + + public init(_ text: Text, + imageSource: ODSImage.Source? = nil, + @ViewBuilder firstButton: @escaping () -> Button, + @ViewBuilder secondButton: @escaping () -> Button) { self.text = text - self.image = image - self.buttonOptions = nil + self.imageSource = imageSource + self.firstButton = firstButton + self.secondButton = secondButton } - /// Initialize the banner with one button. This button - /// can be palaced under or next to the text. + /// Initialize the banner with one button. /// /// - Parameters: - /// - text: Text to be displayed. - /// - image: Painter of the icon. If `nil`, no icon will be displayed. - /// - button: The button - /// - position: The positiobn of the button + /// - text: Text displayed in the banner. + /// - imageSource: Image displayed before the text in a circle area. If `nil`, no image will be displayed. + /// - button: Button with text (only) added under the text. /// - /// - Remarks: The default low emphasis is automatically applied on buttons. - public init( - text: LocalizedStringKey, - image: Image? = nil, - button: ODSButton, - position: OneButtonPosition - ) { + /// - Remarks: The default lowest emphasis is automatically applied on buttons. + + public init(_ text: Text, + imageSource: ODSImage.Source? = nil, + @ViewBuilder button: @escaping () -> Button) + { self.text = text - self.image = image - self.buttonOptions = .oneButton(button, position) + self.imageSource = imageSource + self.firstButton = button + self.secondButton = nil } - /// Initialize the banner with two buttons palaced under the text. + /// Initialize the banner without button. /// /// - Parameters: - /// - text: Text displayed in the button. - /// - image: Painter of the icon. If `nil`, no icon will be displayed. - /// - leadingButton: The first button. - /// - trailingButton: The second button. - /// - /// - Remarks: The default low emphasis is automatically applied on buttons. + /// - text: Text displayed in the banner. + /// - imageSource: Image displayed before the text in a circle area. If `nil`, no image will be displayed. /// - public init( - text: LocalizedStringKey, - image: Image? = nil, - leadingButton: ODSButton, - trailingButton: ODSButton - ) { + public init(_ text: Text, imageSource: ODSImage.Source? = nil) { self.text = text - self.image = image - self.buttonOptions = .twoButtons(leadingButton, trailingButton) + self.imageSource = imageSource + self.firstButton = nil + self.secondButton = nil } // ========== @@ -103,63 +98,47 @@ public struct ODSBanner: View { // ========== public var body: some View { - VStack(spacing: ODSSpacing.m) { - HStack(spacing: ODSSpacing.s) { - image? - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 40.0, height: 40.0, alignment: .center) - .clipShape(Circle()) - .accessibilityHidden(true) - - Text(text) - .odsFont(.subhead) - .frame(maxWidth: .infinity, alignment: .leading) - - nextToTextButton() + VStack(spacing: ODSSpacing.none) { + VStack(alignment: .trailing, spacing: ODSSpacing.none) { + HStack(spacing: ODSSpacing.m) { + if let imageSource = imageSource { + ODSImage(source: imageSource) + .aspectRatio(contentMode: .fill) + .frame(width: 40.0, height: 40.0, alignment: .center) + .clipShape(Circle()) + .accessibilityHidden(true) + } + + text + .odsFont(.subhead) + .frame(maxWidth: .infinity, alignment: .leading) + } + .padding(.top, ODSSpacing.m) + .padding(.bottom, firstButton == nil ? ODSSpacing.m : ODSSpacing.none) + .padding(.horizontal, ODSSpacing.m) + + bottomButtons() } - bottomButtons() + Divider() } - .padding(.horizontal, ODSSpacing.m) - .padding(.vertical, ODSSpacing.m) } // ============= // MARK: Helpers // ============= - @ViewBuilder - private func nextToTextButton() -> some View { - if case let .oneButton(button, position) = buttonOptions, - position == .trailing { - button.modifier(ODSButtonStyleModifier(emphasis: .low)) - } - } - @ViewBuilder private func bottomButtons() -> some View { - if case let .oneButton(button, position) = buttonOptions, - position == .bottom { - HStack { - Spacer() - button.modifier(ODSButtonStyleModifier(emphasis: .low)) - } - } else { - if case let .twoButtons(leadingButton, tralingButton) = buttonOptions { - HStack(spacing: ODSSpacing.xs) { - Spacer() - leadingButton.modifier(ODSButtonStyleModifier(emphasis: .low)) - tralingButton.modifier(ODSButtonStyleModifier(emphasis: .low)) - } + if let firstButton = firstButton { + HStack(spacing: ODSSpacing.none) { + firstButton() + .odsEmphasisButtonStyle(emphasis: .lowest) + secondButton?() + .odsEmphasisButtonStyle(emphasis: .lowest) } } } - - private enum ButtonsOptions { - case oneButton(ODSButton, OneButtonPosition) - case twoButtons(ODSButton, ODSButton) - } } #if DEBUG @@ -169,26 +148,31 @@ struct ODSBanner_Previews: PreviewProvider { ForEach(ColorScheme.allCases, id: \.self) { ODSThemeableView(theme: ODSTheme()) { VStack { - ODSBanner(text: "A short desciption to see text", - image: Image("ods_empty", bundle: Bundle.ods)) + ODSBanner(Text("A short desciption to see text")) .border(.gray) - ODSBanner(text: "A short desciption to see text", - image: Image("ods_empty", bundle: Bundle.ods), - button: ODSButton(text: "Button", emphasis: .low) {}, - position: .trailing) + ODSBanner(Text("A short desciption to see text"), + imageSource: .image(Image("ods_empty", bundle: Bundle.ods))) .border(.gray) - ODSBanner(text: "A short desciption to see text", - image: Image("ods_empty", bundle: Bundle.ods), - button: ODSButton(text: "Button", emphasis: .low) {}, - position: .bottom) + ODSBanner(Text("A short desciption to see text"), + imageSource: .image(Image("ods_empty", bundle: Bundle.ods))) { + Button("Text") { + // Do Something here + } + } .border(.gray) - ODSBanner(text: "A short desciption to see text", - image: Image("ods_empty", bundle: Bundle.ods), - leadingButton: ODSButton(text: "Button", emphasis: .low) {}, - trailingButton: ODSButton(text: "Button", emphasis: .low) {}) + ODSBanner(Text("A short desciption to see text"), + imageSource: .image(Image("ods_empty", bundle: Bundle.ods))) { + Button("Button 1") { + // Do something + } + } secondButton: { + Button("Button 2") { + // Do something + } + } .border(.gray) } } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/BottomSheedHeader.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/BottomSheedHeader.swift index cc3d02a5..c3a27793 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/BottomSheedHeader.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/BottomSheedHeader.swift @@ -96,7 +96,7 @@ struct HeaderPreviewProvider_Previews: PreviewProvider { applyRotation.toggle() } - ODSButton(text: LocalizedStringKey(applyRotation ? "Remove Rotation" : "Apply Rotation"), emphasis: .highest) { + ODSButton(text: Text(applyRotation ? "Remove Rotation" : "Apply Rotation"), emphasis: .high) { applyRotation.toggle() } } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonContent.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonContent.swift index c09a794f..f937a25b 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonContent.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonContent.swift @@ -25,9 +25,26 @@ import SwiftUI // MARK: Content of ODS buttons struct ODSButtonContent: View { - let text: LocalizedStringKey + + // ======================= + // MARK: Stored properties + // ======================= + + let text: Text let image: Image? - let variableWidth: Bool + + // ================= + // MARK: Initiliazer + // ================= + + init(_ text: Text, image: Image? = nil) { + self.text = text + self.image = image + } + + // ========== + // MARK: Body + // ========== var body: some View { HStack(alignment: .center, spacing: ODSSpacing.s) { @@ -36,11 +53,8 @@ struct ODSButtonContent: View { ODSIcon(image, size: 17) } - Text(text) - .odsFont(.bodyBold) + text.odsFont(.bodyBold) } - .padding(.all, ODSSpacing.m) - .frame(minWidth: 50, maxWidth: variableWidth ? nil : .infinity, minHeight: 50) } } @@ -48,11 +62,11 @@ struct ODSButtonContent: View { struct ODSButtonContent_Previews: PreviewProvider { static var previews: some View { VStack { - ODSButtonContent(text: "Text", image: Image(systemName: "wrench"), variableWidth: true) + ODSButtonContent(Text("Text"), image: Image(systemName: "wrench")) .background(.red) - ODSButtonContent(text: "Text", image: Image(systemName: "wrench"), variableWidth: false) + ODSButtonContent(Text("Text"), image: Image(systemName: "wrench")) .background(.blue) - ODSButtonContent(text: "Text", image: Image(systemName: "wrench"), variableWidth: true) + ODSButtonContent(Text("Text"), image: Image(systemName: "wrench")) .background(.red) } .background(.green) diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonStyleLabel.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonStyleLabel.swift new file mode 100644 index 00000000..3548271b --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonStyleLabel.swift @@ -0,0 +1,56 @@ +// +// MIT License +// Copyright (c) 2021 Orange +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +import SwiftUI + +struct ODSButtonStyleLabel: View { + + // ======================= + // MARK: Stored properties + // ======================= + + let configuration: ButtonStyle.Configuration + let foregroundColor: Color + let backgroundColor: Color + let borderColor: Color + let fullWidth: Bool + + // ========== + // MARK: Body + // ========== + + var body: some View { + configuration.label + .padding(ODSSpacing.m) + .frame(minWidth: 50, maxWidth: fullWidth ? .infinity : nil, minHeight: 50) + .foregroundColor(foregroundColor) + .background(backgroundColor) + .buttonBorderShape(ButtonBorderShape.roundedRectangle(radius: 8.0)) + .cornerRadius(8.0) + .overlay( + RoundedRectangle(cornerRadius: 8.0) + .stroke(borderColor, lineWidth: 2.0) + ) + .opacity(configuration.isPressed ? 0.3 : 1.0) + } +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonStyles.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonStyles.swift deleted file mode 100644 index 13ebe0d1..00000000 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonStyles.swift +++ /dev/null @@ -1,132 +0,0 @@ -// -// MIT License -// Copyright (c) 2021 Orange -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the Software), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -// - -import SwiftUI - -// MARK: style modifier - -/// Modifier to apply the right style on the button according to the emphais -struct ODSButtonStyleModifier: ViewModifier { - let emphasis: ODSButton.Emphasis - @Environment(\.theme) var theme - - @ViewBuilder - func body(content: Content) -> some View { - switch emphasis { - case .highest: - let style = ODSShapedButtonStyle(shapeType: .filled, - foregroundColor: theme.componentColors.highestEmphasisText, - backgroundColor: theme.componentColors.accent) - content.buttonStyle(style) - case .high: - let style = ODSShapedButtonStyle(shapeType: .filled, - foregroundColor: Color(UIColor.systemBackground), - backgroundColor: Color(UIColor.label)) - content.buttonStyle(style) - case .medium: - let style = ODSShapedButtonStyle(shapeType: .bordered, - foregroundColor: Color(UIColor.label), - backgroundColor: Color.clear) - content.buttonStyle(style) - case .low: - content - } - } -} - -// MARK: Shaped and filled style -struct ODSShapedButtonStyle: ButtonStyle { - enum ShapeType { - case filled - case bordered - } - - let shapeType: ShapeType - let foregroundColor: Color - let backgroundColor: Color - - @ViewBuilder func makeBody(configuration: Self.Configuration) -> some View { - switch shapeType { - case .filled: - ODSFilledButtonLabel(configuration: configuration, - foregroundColor: foregroundColor, - backgroundColor: backgroundColor) - .opacity(configuration.isPressed ? 0.3 : 1.0) - case .bordered: - ODSBorderedButtonLabel(configuration: configuration, - foregroundColor: foregroundColor, - backgroundColor: backgroundColor) - .opacity(configuration.isPressed ? 0.3 : 1.0) - } - } -} - -private struct ODSFilledButtonLabel: View { - @Environment(\.isEnabled) var isEnabled - let configuration: ButtonStyle.Configuration - let foregroundColor: Color - let backgroundColor: Color - - var body: some View { - if isEnabled { - configuration.label - .foregroundColor(foregroundColor) - .background(backgroundColor) - .cornerRadius(8.0) - } else { - ODSDisabledButtonLabel(configuration: configuration) - } - } -} - -// MARK: Shaped and bordered style -private struct ODSBorderedButtonLabel: View { - @Environment(\.isEnabled) var isEnabled - let configuration: ButtonStyle.Configuration - let foregroundColor: Color - let backgroundColor: Color - - var body: some View { - if isEnabled { - configuration.label - .overlay(RoundedRectangle(cornerRadius: 8.0).stroke(foregroundColor, lineWidth: 2.0)) - } else { - ODSDisabledButtonLabel(configuration: configuration) - } - } -} - -// MARK: Style for disable state -private struct ODSDisabledButtonLabel: View { - let configuration: ButtonStyle.Configuration - - let foregroundColor = Color(UIColor.tertiaryLabel) - let backgroundColor = Color(UIColor.quaternarySystemFill) - - var body: some View { - configuration.label - .foregroundColor(foregroundColor) - .background(backgroundColor) - .cornerRadius(8.0) - } -} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSEmphasisButton+Style.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSEmphasisButton+Style.swift new file mode 100644 index 00000000..21b385db --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSEmphasisButton+Style.swift @@ -0,0 +1,121 @@ +//// +// MIT License +// Copyright (c) 2021 Orange +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +import SwiftUI + +extension View { + func odsEmphasisButtonStyle(emphasis: ODSButton.Emphasis, fullWidth: Bool = false) -> some View { + self.buttonStyle(ODSEmphasisButtonStyle(emphasis: emphasis, fullWidth: fullWidth)) + } +} + +private struct ODSEmphasisButtonStyle: ButtonStyle { + + // ======================= + // MARK: Stored properties + // ======================= + + @Environment(\.isEnabled) var isEnabled + @Environment(\.theme) var theme + private let emphasis: ODSButton.Emphasis + private let fullWidth: Bool + + // ================= + // MARK: Initializer + // ================= + + fileprivate init(emphasis: ODSButton.Emphasis, fullWidth: Bool) { + self.emphasis = emphasis + self.fullWidth = fullWidth + } + + @ViewBuilder + fileprivate func makeBody(configuration: Self.Configuration) -> some View { + ODSButtonStyleLabel(configuration: configuration, + foregroundColor: foregroundColor, + backgroundColor: backgroundColor, + borderColor: borderColor, + fullWidth: fullWidth) + .odsFont(.bodyBold) + } + + // ====================== + // MARK: Private helpers + // ====================== + + private var foregroundColor: Color { + if isEnabled { + switch emphasis { + case .high: + return theme.componentColors.highEmphasisText + case .medium: + return Color(UIColor.systemBackground) + case .low: + return Color(UIColor.label) + case .lowest: + return theme.componentColors.accent + } + } else { + return Color(UIColor.tertiaryLabel) + } + } + + private var backgroundColor: Color { + if isEnabled { + switch emphasis { + case .high: + return theme.componentColors.accent + case .medium: + return Color(UIColor.label) + case .low: + return Color(UIColor.systemBackground) + case .lowest: + return Color.clear + } + } else { + switch emphasis { + case .high, .medium, .low: + return Color(UIColor.quaternarySystemFill) + case .lowest: + return Color.clear + } + } + } + + private var borderColor: Color { + if isEnabled { + switch emphasis { + case .high: + return .clear + case .medium: + return .clear + case .low: + return Color(UIColor.label) + case .lowest: + return .clear + } + } else { + return .clear + } + } +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSFunctionalButton+Style.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSFunctionalButton+Style.swift new file mode 100644 index 00000000..ffd8b48f --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSFunctionalButton+Style.swift @@ -0,0 +1,90 @@ +//// +// MIT License +// Copyright (c) 2021 Orange +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +import SwiftUI + +extension View { + func odsFunctionalButtonStyle(style: ODSFunctionalButton.Style, fullWidth: Bool = false) -> some View { + self.buttonStyle(ODSFunctionalButtonStyleV2(style: style, fullWidth: fullWidth)) + } +} + +private struct ODSFunctionalButtonStyleV2: ButtonStyle { + + // ======================= + // MARK: Stored properties + // ======================= + + @Environment(\.isEnabled) var isEnabled + @Environment(\.theme) var theme + private let style: ODSFunctionalButton.Style + private let fullWidth: Bool + + // ================= + // MARK: Initializer + // ================= + + fileprivate init(style: ODSFunctionalButton.Style, fullWidth: Bool) { + self.style = style + self.fullWidth = fullWidth + } + + @ViewBuilder + fileprivate func makeBody(configuration: Self.Configuration) -> some View { + ODSButtonStyleLabel(configuration: configuration, + foregroundColor: foregroundColor, + backgroundColor: backgroundColor, + borderColor: borderColor, + fullWidth: fullWidth) + .odsFont(.bodyBold) + } + + // ====================== + // MARK: Private helpers + // ====================== + + private var foregroundColor: Color { + if isEnabled { + return Color(UIColor.systemBackground) + } else { + return Color(UIColor.tertiaryLabel) + } + } + + private var backgroundColor: Color { + if isEnabled { + switch style { + case .positive: + return theme.componentColors.functionalPositive + case .negative: + return theme.componentColors.functionalNegative + } + } else { + return Color(UIColor.quaternarySystemFill) + } + } + + private var borderColor: Color { + return .clear + } +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/ODSButton.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/ODSEmphasisButton.swift similarity index 71% rename from OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/ODSButton.swift rename to OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/ODSEmphasisButton.swift index 7617705a..f8af5a37 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/ODSButton.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/ODSEmphasisButton.swift @@ -30,17 +30,25 @@ import SwiftUI /// public struct ODSButton: View { public enum Emphasis: String, CaseIterable { - case highest case high case medium case low + case lowest } - let text: LocalizedStringKey - let image: Image? - let emphasis: Emphasis - let variableWidth: Bool - let action: () -> Void + // ======================= + // MARK: Stored Properties + // ======================= + + private let text: Text + private let image: Image? + private let emphasis: Emphasis + private let fullWidth: Bool + private let action: () -> Void + + // ================== + // MARK: Initializers + // ================== /// Initialize the button. /// @@ -48,30 +56,34 @@ public struct ODSButton: View { /// - text: Text displayed in the button. /// - image: Painter of the icon. If `nil`, no icon will be displayed. /// - emphasis: Controls the style of the button. Use `ODSButton.Emphasis.highest` for an highlighted button style. To get a bordered button use `ODSButton.Emphasis.medium` and get a text only use `ODSButton.Emphasis.low`. - /// - variableWidth: Defines the size of the button layout. Set to `true`, the size of the button is limited to the size of the text added by a padding round it. Set to `false` means button takes all available space horizontally. + /// - fullWidth: Defines the size of the button layout. Set to `true` means button takes all available space horizontally. Set to `false`, the size of the button is limited to the size of the text added by a padding round it. /// - action: Will be called when the user clicks the button. /// public init( - text: LocalizedStringKey, + text: Text, image: Image? = nil, emphasis: Emphasis, - variableWidth: Bool = true, + fullWidth: Bool = false, action: @escaping () -> Void ) { self.text = text self.image = image self.emphasis = emphasis - self.variableWidth = variableWidth + self.fullWidth = fullWidth self.action = action } + // ========== + // MARK: Body + // ========== + public var body: some View { Button { action() } label: { - ODSButtonContent(text: text, image: image, variableWidth: variableWidth) + ODSButtonContent(text, image: image) } - .modifier(ODSButtonStyleModifier(emphasis: emphasis)) + .odsEmphasisButtonStyle(emphasis: emphasis, fullWidth: fullWidth) } } @@ -84,13 +96,13 @@ struct ODSButton_Previews: PreviewProvider { ScrollView { VStack { ForEach(ODSButton.Emphasis.allCases, id: \.rawValue) { emphasis in - ODSButton(text: LocalizedStringKey(emphasis.rawValue), emphasis: emphasis) {} - ODSButton(text: LocalizedStringKey(emphasis.rawValue), emphasis: emphasis) {}.disabled(true) - - ODSButton(text: LocalizedStringKey(emphasis.rawValue), + ODSButton(text: Text(emphasis.rawValue), emphasis: emphasis) {} + ODSButton(text: Text(emphasis.rawValue), emphasis: emphasis) {}.disabled(true) + ODSButton(text: Text(emphasis.rawValue), emphasis: emphasis) {}.disabled(true) + ODSButton(text: Text(emphasis.rawValue), image: Image(systemName: "pencil.tip.crop.circle"), emphasis: emphasis) {} - ODSButton(text: LocalizedStringKey(emphasis.rawValue), + ODSButton(text: Text(emphasis.rawValue), image: Image(systemName: "pencil.tip.crop.circle"), emphasis: emphasis) {}.disabled(true) } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/ODSFunctionalButton.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/ODSFunctionalButton.swift index 305112a6..05fa7283 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/ODSFunctionalButton.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/ODSFunctionalButton.swift @@ -26,18 +26,24 @@ import SwiftUI /// Defines functional buttons (positive or negative) public struct ODSFunctionalButton: View { - @Environment(\.theme) var theme - public enum Style: String, CaseIterable { case negative case positive } - let text: LocalizedStringKey - let image: Image? - let style: ODSFunctionalButton.Style - let variableWidth: Bool - let action: () -> Void + // ======================= + // MARK: Stored Properties + // ======================= + + private let text: Text + private let image: Image? + private let style: ODSFunctionalButton.Style + private let fullWidth: Bool + private let action: () -> Void + + // ================= + // MARK: Initializer + // ================= /// Initialize the button. /// @@ -45,43 +51,34 @@ public struct ODSFunctionalButton: View { /// - text: Text displayed in the button. /// - image: Painter of the icon. If `nil`, no icon will be displayed. /// - style: Controls the style of the button. To get a green/red buttons, you can use `ODSFunctionalButton.Style.positive` or `ODSFunctionalButton.Style.negative`. - /// - variableWidth: Defines the size of the button layout. Set to `true`, the size of the button is limited to the size of the text added by a padding round it. Set to `false` means button takes all available space horizontally. + /// - fullWidth: Defines the size of the button layout. Set to `true` means button takes all available space horizontally. Set to `false`, the size of the button is limited to the size of the text added by a padding round it. /// - action: Will be called when the user clicks the button. /// public init( - text: LocalizedStringKey, + text: Text, image: Image? = nil, style: ODSFunctionalButton.Style, - variableWidth: Bool = true, + fullWidth: Bool = false, action: @escaping () -> Void ) { self.text = text self.image = image self.style = style - self.variableWidth = variableWidth + self.fullWidth = fullWidth self.action = action } + // ========== + // MARK: Body + // ========== + public var body: some View { Button { action() } label: { - ODSButtonContent(text: text, image: image, variableWidth: variableWidth) - } - .buttonStyle(ODSShapedButtonStyle(shapeType: .filled, foregroundColor: foregroundColor, backgroundColor: backgroundColor)) - } - - var foregroundColor: Color { - Color(UIColor.systemBackground) - } - - var backgroundColor: Color { - switch style { - case .negative: - return theme.componentColors.functionalNegative - case .positive: - return theme.componentColors.functionalPositive + ODSButtonContent(text, image: image) } + .odsFunctionalButtonStyle(style: style, fullWidth: fullWidth) } } @@ -91,14 +88,14 @@ struct ODSFunctionalButton_Previews: PreviewProvider { VStack(spacing: ODSSpacing.xl) { VStack { Text("Without image") - ODSFunctionalButton(text: "Enabled", style: .negative) {} - ODSFunctionalButton(text: "Disabled", style: .negative) {}.disabled(true) + ODSFunctionalButton(text: Text("Enabled"), style: .negative) {} + ODSFunctionalButton(text: Text("Disabled") , style: .negative) {}.disabled(true) } VStack { Text("Without image") - ODSFunctionalButton(text: "Enabled", image: Image(systemName: "pencil.tip"), style: ODSFunctionalButton.Style.negative) {} - ODSFunctionalButton(text: "Disabled", image: Image(systemName: "pencil.tip"), style: ODSFunctionalButton.Style.negative) {}.disabled(true) + ODSFunctionalButton(text: Text("Enabled"), image: Image(systemName: "pencil.tip"), style: ODSFunctionalButton.Style.negative) {} + ODSFunctionalButton(text: Text("Disabled"), image: Image(systemName: "pencil.tip"), style: ODSFunctionalButton.Style.negative) {}.disabled(true) } } } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/ODSIconButton.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/ODSIconButton.swift index 318bc7a8..2bc1fbdb 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/ODSIconButton.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/ODSIconButton.swift @@ -27,11 +27,18 @@ import SwiftUI /// Simple button with icon. /// public struct ODSIconButton: View { - @Environment(\.theme) private var theme + + // ======================= + // MARK: Stored Properties + // ======================= let image: Image let action: () -> Void + // ================== + // MARK: Initializers + // ================== + /// Initialize the button. /// /// - Parameters: @@ -43,12 +50,15 @@ public struct ODSIconButton: View { self.action = action } + // ========== + // MARK: Body + // ========== + public var body: some View { Button { action() } label: { ODSIcon(image) - .foregroundColor(theme.componentColors.accent) } } } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardHorizontal.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardHorizontal.swift index 28f5b12d..afa74c1c 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardHorizontal.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardHorizontal.swift @@ -23,183 +23,195 @@ import SwiftUI -/// Model used to configure the `ODSCardHorizontal` card. -public struct ODSCardHorizontalModel: Identifiable { - let title: String - let subtitle: String? - let supportingText: String? - let imageSource: ODSImage.Source - let imagePosition: ImagePosition - let dividerEnabled: Bool +/// +/// ODS Card. +/// +/// This is a full width card displayed with an image on the left or on the right. +/// This card is composed of two parts: +/// - Media: (today an image) +/// - Content: with a title, an optional subtitle, an optional text and optional buttons (zero up to two) +/// - public enum ImagePosition: Int { - case trailing = 0 +public struct ODSCardHorizontal: View { + public enum ImagePosition { + case trailing case leading } - /// Initialization + private let title: Text + private let subtitle: Text? + private let text: Text? + private let imageSource: ODSImage.Source + private let imagePosition: ImagePosition + private let dividerEnabled: Bool + private let firstButton: (() -> Button)? + private let secondButton: (() -> Button)? + + // ================= + // MARK: Initializer + // ================= + + /// Initialization without button. /// /// - Parameters: /// - title: The title to be displayed in the card. - /// - subtitle: Optional subtitle to be displayed in the card. /// - imageSource: The image to be displayed in the card. /// - imagePosition: The side where image is placed. - /// - supportingText: Optional text description to be displayed in the card. The text displaying is limited to two lines (truncated tail). - /// - dividerEnabled: Add a divider at the top of the buttons area. + /// - subtitle: Optional subtitle to be displayed in the card. + /// - text: Optional text description to be displayed in the card. The text displaying is limited to two lines (truncated tail). /// public init( - title: String, - subtitle: String? = nil, + title: Text, imageSource: ODSImage.Source, imagePosition: ImagePosition = .leading, - supportingText: String? = nil, - dividerEnabled: Bool = true + subtitle: Text? = nil, + text: Text? = nil ) { self.title = title self.subtitle = subtitle self.imageSource = imageSource self.imagePosition = imagePosition - self.supportingText = supportingText - self.dividerEnabled = dividerEnabled + self.text = text + self.dividerEnabled = false + self.firstButton = nil + self.secondButton = nil } - /// The identifier based on the title. - public var id: String { - title - } -} - -/// -/// ODS Card. -/// -/// This is a full width card displayed with an image on the left or on the right. -/// This card is composed of two parts: -/// - Media: (today an image) -/// - Content: with a title, an optinal subtitle, an optinal supporting text and optional buttons (zero up to two) -/// -/// The card is configured using the model `ODSCardHorizontalModel` and optional action buttons -/// can be provided through ViewBuilders `buttonContent1` and `buttonContent2`. -/// -/// Those view builders are usefull to provide buttons managed somewhere else to handle actions, manage disable state, apply style,... -/// -public struct ODSCardHorizontal: View where ButtonContent1: View, ButtonContent2: View { - - private var model: ODSCardHorizontalModel - private var buttonContent1: () -> ButtonContent1 - private var buttonContent2: () -> ButtonContent2 - - /// Initialization with two buttons. + /// Initialization with one button. /// /// - Parameters: - /// - model: The model to configure the card. - /// - buttonContent1: The button1 view builder - /// - buttonContent2: The button2 view builder + /// - title: The title to be displayed in the card. + /// - imageSource: The image to be displayed in the card. + /// - imagePosition: The side where image is placed. + /// - subtitle: Optional subtitle to be displayed in the card. + /// - text: Optional text description to be displayed in the card. The text + /// displaying is limited to two lines (truncated tail). + /// - button: The optional first (leading) button. + /// - dividerEnabled: Optional divider added at the top of the buttons area. /// public init( - model: ODSCardHorizontalModel, - @ViewBuilder buttonContent1: @escaping () -> ButtonContent1, - @ViewBuilder buttonContent2: @escaping () -> ButtonContent2 + title: Text, + imageSource: ODSImage.Source, + imagePosition: ImagePosition = .leading, + subtitle: Text? = nil, + text: Text? = nil, + dividerEnabled: Bool = true, + @ViewBuilder button: @escaping () -> Button ) { - self.model = model - self.buttonContent1 = buttonContent1 - self.buttonContent2 = buttonContent2 - } -} + self.title = title + self.subtitle = subtitle + self.imageSource = imageSource + self.imagePosition = imagePosition + self.text = text -extension ODSCardHorizontal where ButtonContent2 == EmptyView { + self.dividerEnabled = dividerEnabled + self.firstButton = button + self.secondButton = nil + } - /// Initialization with one button. + /// Initialization with two buttons. /// /// - Parameters: - /// - model: The model to configure the card. - /// - buttonContent1: The button1 view builder + /// - title: The title to be displayed in the card. + /// - imageSource: The image to be displayed in the card. + /// - imagePosition: The side where image is placed. + /// - subtitle: Optional subtitle to be displayed in the card. + /// - text: Optional text description to be displayed in the card. The text + /// displaying is limited to two lines (truncated tail). + /// - firstButton: The optional first (leading) button. + /// - secondButton: The optional second (trailing) button. + /// - dividerEnabled: Optional divider added at the top of the buttons area. /// - public init(model: ODSCardHorizontalModel, @ViewBuilder buttonContent1: @escaping () -> ButtonContent1) - { - self.model = model - self.buttonContent1 = buttonContent1 - buttonContent2 = { EmptyView() } - } -} - -extension ODSCardHorizontal where ButtonContent1 == EmptyView, ButtonContent2 == EmptyView { + public init( + title: Text, + imageSource: ODSImage.Source, + imagePosition: ImagePosition = .leading, + subtitle: Text? = nil, + text: Text? = nil, + dividerEnabled: Bool = true, + @ViewBuilder firstButton: @escaping () -> Button, + @ViewBuilder secondButton: @escaping () -> Button + ) { + self.title = title + self.subtitle = subtitle + self.imageSource = imageSource + self.imagePosition = imagePosition + self.text = text - /// Initialization without any button. - /// - /// - Parameter model: The model to configure the card. - /// - public init(model: ODSCardHorizontalModel) { - self.model = model - buttonContent1 = { EmptyView() } - buttonContent2 = { EmptyView() } + self.dividerEnabled = dividerEnabled + self.firstButton = firstButton + self.secondButton = secondButton } -} - -// MARK: View body implementation -extension ODSCardHorizontal { + // ========== + // MARK: Body + // ========== public var body: some View { VStack(spacing: ODSSpacing.none) { HStack(alignment: .center, spacing: ODSSpacing.none) { - Group { - if case .leading = model.imagePosition { - image - } + if case .leading = imagePosition { + image + } - VStack(alignment: .leading, spacing: ODSSpacing.xs) { - Text(model.title) - .odsFont(.bodyBold) - .frame(maxWidth: .infinity, alignment: .leading) + VStack(alignment: .leading, spacing: ODSSpacing.xs) { + title + .odsFont(.bodyBold) + .frame(maxWidth: .infinity, alignment: .leading) - if let subtitle = model.subtitle, !subtitle.isEmpty { - Text(subtitle) - .frame(maxWidth: .infinity, alignment: .leading) - } - if let supportingText = model.supportingText, !supportingText.isEmpty { - Text(supportingText) - .lineLimit(2) - .frame(maxWidth: .infinity, alignment: .leading) - } - } - .foregroundColor(.primary) - .padding(.all, ODSSpacing.m) + subtitle? + .frame(maxWidth: .infinity, alignment: .leading) - if case .trailing = model.imagePosition { - image - } + text? + .lineLimit(2) + .frame(maxWidth: .infinity, alignment: .leading) } - } - .frame(minHeight: 128) + .foregroundColor(.primary) + .padding(.all, ODSSpacing.m) - if model.dividerEnabled { - Divider() + if case .trailing = imagePosition { + image + } } + .frame(minHeight: 128) - // Add padding on buttons to avoid to have extra padding on - // HStack even if there are no view on buttons. - HStack(alignment: .center, spacing: ODSSpacing.m) { - buttonContent1().padding(.top, ODSSpacing.m) - .padding(.bottom, ODSSpacing.m) - buttonContent2().padding(.top, ODSSpacing.m) - .padding(.bottom, ODSSpacing.m) - Spacer() - } - .padding(.horizontal, ODSSpacing.m) + buttons() } .background(ODSInternalColor.cardBackground.color) .cornerRadius(10) .shadow(radius: ODSSpacing.xs) .padding(.all, ODSSpacing.s) } + + // ===================== + // MARK: Private Helpers + // ===================== private var image: some View { - ODSImage(source: model.imageSource) + ODSImage(source: imageSource) .accessibilityHidden(true) .frame(width: 128) .clipped() } + + @ViewBuilder + private func buttons() -> some View { + if let firstButton = firstButton { + if dividerEnabled { + Divider() + } + + HStack(alignment: .center, spacing: ODSSpacing.none) { + firstButton() + .odsEmphasisButtonStyle(emphasis: .lowest) + secondButton?() + .odsEmphasisButtonStyle(emphasis: .lowest) + + Spacer() + } + } + } } #if DEBUG @@ -221,41 +233,29 @@ struct ODSCardHorizontal_Previews: PreviewProvider { } } - struct ButtonAction: View { - let text: String - let action: () -> Void - - var body: some View { - ODSButton(text: LocalizedStringKey(text), emphasis: .highest, variableWidth: true, action: action) - } - } - - static let model = ODSCardHorizontalModel( - title: ODSCCardPreviewData.title, - subtitle: ODSCCardPreviewData.subtitle, - imageSource: .image(ODSCCardPreviewData.image), - imagePosition: .leading, - supportingText: ODSCCardPreviewData.supportingText) - struct TestView: View { @State var showTextInToast: String? - @State var disableButton1 = false var body: some View { ScrollView { - ODSCardHorizontal(model: ODSCardHorizontal_Previews.model) { - ButtonAction(text: "Button 1") { - showTextInToast = "Button 1 Clicked" + ODSCardHorizontal( + title: Text(ODSCCardPreviewData.title), + imageSource: .image(ODSCCardPreviewData.image), + imagePosition: .leading, + subtitle: Text(ODSCCardPreviewData.subtitle), + text: Text(ODSCCardPreviewData.supportingText), + dividerEnabled: true) { + Button("Button 1") { + showTextInToast = "Button 1 Clicked" + } + } secondButton: { + Button("Button 2") { + showTextInToast = "Button 2 Clicked" + } } - .disabled(disableButton1) - } buttonContent2: { - ButtonAction(text: "\(disableButton1 ? "Enable" : "Disable") Button 1") { - disableButton1.toggle() + .onTapGesture { + showTextInToast = "Card tapped" } - } - .onTapGesture { - showTextInToast = "Card tapped" - } Toast(showText: $showTextInToast) } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardVerticalHeaderFirst.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardVerticalHeaderFirst.swift index 34289c64..5fd95135 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardVerticalHeaderFirst.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardVerticalHeaderFirst.swift @@ -23,120 +23,119 @@ import SwiftUI -/// Model used to configure the `ODSCardVerticalHeaderFirst` card. -public struct ODSCardVerticalHeaderFirstModel: Identifiable { - let title: String - let subtitle: String? - let thumbnail: Image? - let imageSource: ODSImage.Source - let supportingText: String? +/// +/// ODS Card. +/// +/// This is a full width card displayed with a title and a thumbnail on top as first element. +/// This card is composed of three parts: +/// - Header: with a title, an optional subtitle and an optional thmubnail +/// - Media: (today an image) +/// - Content: with an optional text and optional buttons (zero up to two) +/// +public struct ODSCardVerticalHeaderFirst: View { + + private let title: Text + private let subtitle: Text? + private let thumbnail: Image? + private let imageSource: ODSImage.Source + private let text: Text? + private let firstButton: (() -> Button)? + private let secondButton: (() -> Button)? + + // ================= + // MARK: Initializer + // ================= - /// Initilize the model + /// Initialization without button. /// /// - Parameters: /// - title: The title to be displayed in the card. - /// - thumbnail: The optional thumbnail: avatar, logo or icon. - /// - subtitle: Optional subtitle to be displayed in the card. /// - imageSource: The image to be displayed in the card. - /// - supportingText Optional text description to be displayed in the card. + /// - subtitle: Optional subtitle to be displayed in the card. + /// - thumbnail: The optional thumbnail: avatar, logo or icon. + /// - text: Optional text description to be displayed in the card. /// public init( - title: String, - subtitle: String? = nil, - thumbnail: Image? = nil, + title: Text, imageSource: ODSImage.Source, - supportingText: String? = nil + subtitle: Text?, + thumbnail: Image?, + text: Text? = nil ) { self.title = title self.subtitle = subtitle self.thumbnail = thumbnail self.imageSource = imageSource - self.supportingText = supportingText - } - - /// The identifier based on the title. - public var id: String { - title + self.text = text + self.firstButton = nil + self.secondButton = nil } -} -/// -/// ODS Card. -/// -/// This is a full width card displayed with a title and a thumbnail on top as first element. -/// This card is composed of three parts: -/// - Header: with a title, an optinal subtitle and an optinal thmubnail -/// - Media: (today an image) -/// - Content: with an optinal supporting text and optional buttons (zero up to two) -/// -/// The card is configured using the model `ODSCardVerticalHeaderFirstModel` and optional action buttons -/// can be provided through ViewBuilders `buttonContent1` and `buttonContent2`. -/// -/// Those view builder are usefull to provide buttons managed somewhere else to handle actions, manage disable state, apply style,... -/// -public struct ODSCardVerticalHeaderFirst: View where ButtonContent1: View, ButtonContent2: View { - - private var buttonContent1: () -> ButtonContent1 - private var buttonContent2: () -> ButtonContent2 - private let model: ODSCardVerticalHeaderFirstModel - - /// Initialization with two buttons. + /// Initialization with one button. /// /// - Parameters: - /// - model: The model to configure the card. - /// - buttonContent1: The button1 view builder - /// - buttonContent2: The button2 view builder + /// - title: The title to be displayed in the card. + /// - imageSource: The image to be displayed in the card. + /// - subtitle: Optional subtitle to be displayed in the card. + /// - thumbnail: The optional thumbnail: avatar, logo or icon. + /// - text: Optional text description to be displayed in the card. + /// - button: The optional button. /// public init( - model: ODSCardVerticalHeaderFirstModel, - @ViewBuilder buttonContent1: @escaping () -> ButtonContent1, - @ViewBuilder buttonContent2: @escaping () -> ButtonContent2 + title: Text, + imageSource: ODSImage.Source, + subtitle: Text?, + thumbnail: Image?, + text: Text? = nil, + @ViewBuilder button: @escaping () -> Button ) { - self.model = model - self.buttonContent1 = buttonContent1 - self.buttonContent2 = buttonContent2 + self.title = title + self.subtitle = subtitle + self.thumbnail = thumbnail + self.imageSource = imageSource + self.text = text + self.firstButton = button + self.secondButton = nil } -} - -extension ODSCardVerticalHeaderFirst where ButtonContent2 == EmptyView { - /// Initialization with one button. + /// Initialization with two buttons. /// /// - Parameters: - /// - model: The model to configure the card. - /// - buttonContent1: The button1 view builder + /// - title: The title to be displayed in the card. + /// - imageSource: The image to be displayed in the card. + /// - subtitle: Optional subtitle to be displayed in the card. + /// - thumbnail: The optional thumbnail: avatar, logo or icon. + /// - text: Optional text description to be displayed in the card. + /// - firstButton: The optional first (leading) button. + /// - secondButton: The optional second (trailing) button. /// public init( - model: ODSCardVerticalHeaderFirstModel, - @ViewBuilder buttonContent1: @escaping () -> ButtonContent1 + title: Text, + imageSource: ODSImage.Source, + subtitle: Text? = nil, + thumbnail: Image? = nil, + text: Text? = nil, + dividerEnabled: Bool = true, + @ViewBuilder firstButton: @escaping () -> Button, + @ViewBuilder secondButton: @escaping () -> Button ) { - self.model = model - self.buttonContent1 = buttonContent1 - buttonContent2 = { EmptyView() } - } -} - -extension ODSCardVerticalHeaderFirst where ButtonContent1 == EmptyView, ButtonContent2 == EmptyView { - - /// Initialization without any button. - /// - /// - Parameter model: The model to configure the card. - /// - public init(model: ODSCardVerticalHeaderFirstModel) { - self.model = model - buttonContent1 = { EmptyView() } - buttonContent2 = { EmptyView() } + self.title = title + self.subtitle = subtitle + self.thumbnail = thumbnail + self.imageSource = imageSource + self.text = text + self.firstButton = firstButton + self.secondButton = secondButton } -} -// MARK: View body implementation - -extension ODSCardVerticalHeaderFirst { + // ========== + // MARK: Body + // ========== public var body: some View { VStack(alignment: .leading, spacing: ODSSpacing.none) { HStack(alignment: .center, spacing: ODSSpacing.s) { - model.thumbnail? + thumbnail? .resizable() .aspectRatio(contentMode: .fill) .frame(width: 44.0, height: 44.0, alignment: .center) @@ -144,14 +143,12 @@ extension ODSCardVerticalHeaderFirst { .accessibilityHidden(true) VStack(alignment: .leading, spacing: ODSSpacing.none) { - Text(model.title) + title .odsFont(.bodyBold) .frame(maxWidth: .infinity, alignment: .leading) - if let subtitle = model.subtitle, !subtitle.isEmpty { - Text(subtitle) - .odsFont(.bodyRegular) - } + subtitle? + .odsFont(.bodyRegular) } } .multilineTextAlignment(.leading) @@ -160,54 +157,47 @@ extension ODSCardVerticalHeaderFirst { .padding(.horizontal, ODSSpacing.m) .layoutPriority(100) - ODSImage(source: model.imageSource) + ODSImage(source: imageSource) .aspectRatio(contentMode: .fill) .accessibilityHidden(true) VStack(alignment: .leading, spacing: ODSSpacing.none) { - if let supportingText = model.supportingText, !supportingText.isEmpty { - Text(supportingText) - .odsFont(.bodyRegular) - .multilineTextAlignment(.leading) - .padding(.horizontal, ODSSpacing.m) - .padding(.top, ODSSpacing.s) - } - - // Add padding on buttons to avoid to have extra padding on - // HStack even if there are no view on buttons. - HStack(spacing: ODSSpacing.m) { - buttonContent1().padding(.top, ODSSpacing.m) - buttonContent2().padding(.top, ODSSpacing.m) - } - .padding(.horizontal, ODSSpacing.m) + text? + .odsFont(.bodyRegular) + .multilineTextAlignment(.leading) + .padding(.horizontal, ODSSpacing.m) + .padding(.top, ODSSpacing.s) + .padding(.bottom, firstButton == nil ? ODSSpacing.m : ODSSpacing.none) } - .modifier(PaddingModifier()) + + buttons() } .modifier(CardShadowModifier()) } -} - -// MARK: Padding modifier -private struct PaddingModifier: ViewModifier { - @State private var height: CGFloat? + // ===================== + // MARK: Private Helpers + // ===================== - private var bottomPadding: CGFloat { - height == 0 ? ODSSpacing.none : ODSSpacing.m + private var image: some View { + ODSImage(source: imageSource) + .accessibilityHidden(true) + .frame(width: 128) + .clipped() } - private var topPadding: CGFloat { - height == 0 ? ODSSpacing.none : ODSSpacing.xs - } + @ViewBuilder + private func buttons() -> some View { + if let firstButton = firstButton { + HStack(alignment: .center, spacing: ODSSpacing.none) { + firstButton() + .odsEmphasisButtonStyle(emphasis: .lowest) + secondButton?() + .odsEmphasisButtonStyle(emphasis: .lowest) - func body(content: Content) -> some View { - content - .readSize { size in - print("size\(size.height)") - height = size.height + Spacer() } - .padding(.top, topPadding) - .padding(.bottom, bottomPadding) + } } } @@ -231,41 +221,28 @@ struct ODSCardVerticalHeaderFirst_Previews: PreviewProvider { } } - struct ButtonAction: View { - let text: String - let action: () -> Void - - var body: some View { - ODSButton(text: LocalizedStringKey(text), emphasis: .highest, variableWidth: true, action: action) - } - } - - static let model = ODSCardVerticalHeaderFirstModel( - title: ODSCCardPreviewData.title, - subtitle: ODSCCardPreviewData.subtitle, - thumbnail: ODSCCardPreviewData.thumbnail, - imageSource: .image(ODSCCardPreviewData.image), - supportingText: ODSCCardPreviewData.supportingText) - struct TestView: View { @State var showTextInToast: String? - @State var disableButton1 = false var body: some View { ScrollView { - ODSCardVerticalHeaderFirst(model: ODSCardVerticalHeaderFirst_Previews.model) { - ButtonAction(text: "Button 1") { - showTextInToast = "Button 1 Clicked" + ODSCardVerticalHeaderFirst( + title: Text(ODSCCardPreviewData.title), + imageSource: .image(ODSCCardPreviewData.image), + subtitle: Text(ODSCCardPreviewData.subtitle), + thumbnail: ODSCCardPreviewData.thumbnail, + text: Text(ODSCCardPreviewData.supportingText)) { + Button("Button 1") { + showTextInToast = "Button 1 Clicked" + } + } secondButton: { + Button("Button 2") { + showTextInToast = "Button 2 Clicked" + } } - .disabled(disableButton1) - } buttonContent2: { - ButtonAction(text: "\(disableButton1 ? "Enable" : "Disable") Button 1") { - disableButton1.toggle() + .onTapGesture { + showTextInToast = "Card tapped" } - } - .onTapGesture { - showTextInToast = "Card tapped" - } Toast(showText: $showTextInToast) } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardVerticalImageFirst.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardVerticalImageFirst.swift index f1e1919d..269e50ce 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardVerticalImageFirst.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardVerticalImageFirst.swift @@ -22,38 +22,6 @@ import SwiftUI -/// Model used to configure the `ODSCardVerticalImageFirst` card. -public struct ODSCardVerticalImageFirstModel: Identifiable { - let title: String - let subtitle: String? - let imageSource: ODSImage.Source - let supportingText: String? - - /// Initialization - /// - /// - Parameters: - /// - title: The title to be displayed in the card. - /// - subtitle: Optional subtitle to be displayed in the card. - /// - imageSource: The image to be displayed in the card. - /// - supportingText: Optional text description to be displayed in the card. - /// - public init( - title: String, - subtitle: String? = nil, - imageSource: ODSImage.Source, - supportingText: String? = nil - ) { - self.title = title - self.subtitle = subtitle - self.imageSource = imageSource - self.supportingText = supportingText - } - - /// The identifier based on the title. - public var id: String { - title - } -} /// /// ODS Card. @@ -61,104 +29,141 @@ public struct ODSCardVerticalImageFirstModel: Identifiable { /// This is a full width card displayed with an image as first element. /// This card is composed of two parts: /// - Media: (today an image) -/// - Content: with a title, an optinal subtitle, an optinal supporting text and optional buttons (zero up to two) -/// -/// The card is configured using the model `ODSCardVerticalImageFirstModel` and optional action buttons -/// can be provided through ViewBuilders `buttonContent1` and `buttonContent2`. +/// - Content: with a title, an optional subtitle, an optional supporting text and optional buttons (zero up to two) /// -/// Those view builders are usefull to provide buttons managed somewhere else to handle actions, manage disable state, apply style,... -/// -public struct ODSCardVerticalImageFirst: View where ButtonContent1: View, ButtonContent2: View { +public struct ODSCardVerticalImageFirst: View { - var model: ODSCardVerticalImageFirstModel - var buttonContent1: () -> ButtonContent1 - var buttonContent2: () -> ButtonContent2 + private let title: Text + private let subtitle: Text? + private let imageSource: ODSImage.Source + private let text: Text? + private let firstButton: (() -> Button)? + private let secondButton: (() -> Button)? - /// Initialization with two buttons. + /// Initialization without button. /// /// - Parameters: - /// - model: The model to configure the card. - /// - buttonContent1: The button1 view builder - /// - buttonContent2: The button2 view builder + /// - title: The title. + /// - imageSource: The source of the image. + /// - subtitle: Optional subtitle. + /// - text: Optional text description. /// public init( - model: ODSCardVerticalImageFirstModel, - @ViewBuilder buttonContent1: @escaping () -> ButtonContent1, - @ViewBuilder buttonContent2: @escaping () -> ButtonContent2 + title: Text, + imageSource: ODSImage.Source, + subtitle: Text? = nil, + text: Text? = nil ) { - self.model = model - self.buttonContent1 = buttonContent1 - self.buttonContent2 = buttonContent2 + self.title = title + self.subtitle = subtitle + self.imageSource = imageSource + self.text = text + self.firstButton = nil + self.secondButton = nil } /// Initialization with one button. /// /// - Parameters: - /// - model: The model to configure the card. - /// - buttonContent1: The button1 view builder + /// - title: The title. + /// - imageSource: The source of the image. + /// - subtitle: Optional subtitle. + /// - text: Optional text description. + /// - button: The button with text only (lowest emphasis). /// - public init(model: ODSCardVerticalImageFirstModel, @ViewBuilder buttonContent1: @escaping () -> ButtonContent1) where ButtonContent2 == EmptyView - { - self.init(model: model, buttonContent1: buttonContent1) { - EmptyView() - } + public init( + title: Text, + imageSource: ODSImage.Source, + subtitle: Text? = nil, + text: Text? = nil, + @ViewBuilder button: @escaping () -> Button + ) { + self.title = title + self.subtitle = subtitle + self.imageSource = imageSource + self.text = text + self.firstButton = button + self.secondButton = nil } - /// Initialization without any button. + /// Initialization with two buttons. /// - /// - Parameter model: The model to configure the card. + /// - Parameters: + /// - title: The title. + /// - imageSource: The source of the image. + /// - subtitle: Optional subtitle. + /// - text: Optional text description. + /// - firstButton: The first (leading) button text. + /// - secondButton: The second (trailing) button text. /// - public init(model: ODSCardVerticalImageFirstModel) where ButtonContent1 == EmptyView, ButtonContent2 == EmptyView { - self.init(model: model) { - EmptyView() - } buttonContent2: { - EmptyView() - } + public init( + title: Text, + imageSource: ODSImage.Source, + subtitle: Text? = nil, + text: Text? = nil, + @ViewBuilder firstButton: @escaping () -> Button, + @ViewBuilder secondButton: @escaping () -> Button + ) { + self.title = title + self.subtitle = subtitle + self.imageSource = imageSource + self.text = text + self.firstButton = firstButton + self.secondButton = secondButton } -} -// MARK: View body implementation -extension ODSCardVerticalImageFirst { + // ========== + // MARK: Body + // ========== public var body: some View { VStack(alignment: .leading, spacing: ODSSpacing.none) { Group { - ODSImage(source: model.imageSource) + ODSImage(source: imageSource) .aspectRatio(contentMode: .fill) .accessibilityHidden(true) VStack(alignment: .leading, spacing: ODSSpacing.xs) { - Text(model.title) + title .odsFont(.bodyBold) .frame(maxWidth: .infinity, alignment: .leading) - if let subtitle = model.subtitle, !subtitle.isEmpty { - Text(subtitle) - .frame(maxWidth: .infinity, alignment: .leading) - } - if let supportingText = model.supportingText, !supportingText.isEmpty { - Text(supportingText) + subtitle? + .frame(maxWidth: .infinity, alignment: .leading) + + text? .frame(maxWidth: .infinity, alignment: .leading) - } } .foregroundColor(.primary) .padding(.horizontal, ODSSpacing.m) .padding(.top, ODSSpacing.m) + .padding(.bottom, firstButton == nil ? ODSSpacing.m : ODSSpacing.none) } .multilineTextAlignment(.leading) - // Add padding on buttons to avoid to have extra padding on - // HStack even if there are no view on buttons. - HStack(spacing: ODSSpacing.m) { - buttonContent1().padding(.top, ODSSpacing.m) - buttonContent2().padding(.top, ODSSpacing.m) - } - .padding(.horizontal, ODSSpacing.m) - .padding(.bottom, ODSSpacing.m) + buttons() } .modifier(CardShadowModifier()) } + + // ===================== + // MARK: Private Helpers + // ===================== + + @ViewBuilder + private func buttons() -> some View { + if let firstButton = firstButton { + HStack(alignment: .center, spacing: ODSSpacing.none) { + firstButton() + .odsEmphasisButtonStyle(emphasis: .lowest) + secondButton?() + .odsEmphasisButtonStyle(emphasis: .lowest) + + Spacer() + } + } + } } // MARK: Previews @@ -181,35 +186,24 @@ struct ODSCardVerticalImageFirst_Previews: PreviewProvider { } } - struct ButtonAction: View { - let text: String - let action: () -> Void - - var body: some View { - ODSButton(text: LocalizedStringKey(text), emphasis: .highest, variableWidth: true, action: action) - } - } - - static let model = ODSCardVerticalImageFirstModel( - title: ODSCCardPreviewData.title, - subtitle: ODSCCardPreviewData.subtitle, - imageSource: .image(ODSCCardPreviewData.image), - supportingText: ODSCCardPreviewData.supportingText) struct TestView: View { @State var showTextInToast: String? - @State var disableButton1 = false var body: some View { ScrollView { - ODSCardVerticalImageFirst(model: ODSCardVerticalImageFirst_Previews.model) { - ButtonAction(text: "Button 1") { + ODSCardVerticalImageFirst( + title: Text(ODSCCardPreviewData.title), + imageSource: .image(ODSCCardPreviewData.image), + subtitle: Text(ODSCCardPreviewData.subtitle), + text: Text(ODSCCardPreviewData.supportingText) + ) { + Button("Button 1") { showTextInToast = "Button 1 Clicked" } - .disabled(disableButton1) - } buttonContent2: { - ButtonAction(text: "\(disableButton1 ? "Enable" : "Disable") Button 1") { - disableButton1.toggle() + } secondButton: { + Button("Button 2") { + showTextInToast = "Button 2 Clicked" } } .onTapGesture { diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Configuration/ODSAboutApplicationInformation.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Configuration/ODSAboutApplicationInformation.swift index 0e396d9e..e15eaa0a 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Configuration/ODSAboutApplicationInformation.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Configuration/ODSAboutApplicationInformation.swift @@ -92,6 +92,8 @@ public struct ODSAboutShareTheApplication { /// - subject: The subject, if the application is shared with email. /// - description: Can be used to describe the sharing (Content of the email, SMS, ...). /// + /// Remark: Do not copy the url in the description because it can be presented twice (e.g. in copy action) + /// public init(storeUrl: URL, subject: String, description: String) { self.storeUrl = storeUrl self.subject = subject diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/ApplicationInformation/ApplicationInformation.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/ApplicationInformation/ApplicationInformation.swift index e290d143..d7c05999 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/ApplicationInformation/ApplicationInformation.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/ApplicationInformation/ApplicationInformation.swift @@ -40,11 +40,12 @@ struct AboutApplicationInformation: View { Text(applicationInformation.name) .odsFont(.largeTitle) .fixedSize(horizontal: false, vertical: true) + .accessibilityAddTraits(.isHeader) if applicationInformation.shareConfiguration != nil || applicationInformation.onFeedbackClicked != nil { HStack(spacing: ODSSpacing.none) { if let shareConfiguration = applicationInformation.shareConfiguration { - ODSButton(text: "Share", image: Image("ic_share", bundle: Bundle.ods), emphasis: .low) { + ODSButton(text: Text("Share"), image: Image("ic_share", bundle: Bundle.ods), emphasis: .lowest) { ShareSheet.show(content: shareConfiguration.description, subject: shareConfiguration.subject, url: shareConfiguration.storeUrl) } .buttonStyle(PlainButtonStyle()) @@ -52,7 +53,7 @@ struct AboutApplicationInformation: View { } if let onFeedbackClicked = applicationInformation.onFeedbackClicked { - ODSButton(text: "Feedback", image: Image("ic_comments", bundle: Bundle.ods), emphasis: .low, action: onFeedbackClicked) + ODSButton(text: Text("Feedback"), image: Image("ic_comments", bundle: Bundle.ods), emphasis: .lowest, action: onFeedbackClicked) .buttonStyle(PlainButtonStyle()) .foregroundColor(Color.accentColor) } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/ODSGridOfCards.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/ODSGridOfCards.swift index 2497126f..2eb7d1c4 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/ODSGridOfCards.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/ODSGridOfCards.swift @@ -29,16 +29,26 @@ import SwiftUI /// public struct ODSGridOfCards: View { - public let cardModels: [ODSCardSmallModel] + // ======================= + // MARK: Stored properties + // ======================= - let columns = [ - GridItem(.adaptive(minimum: 150.0), spacing: ODSSpacing.none, alignment: .topLeading), - ] + private let cardModels: [ODSCardSmallModel] + private let columns: [GridItem] + + // ================= + // MARK: Initializer + // ================= public init(cardModels: [ODSCardSmallModel]) { self.cardModels = cardModels + self.columns = [GridItem(.adaptive(minimum: 150.0), spacing: ODSSpacing.none, alignment: .center)] } + // ========== + // MARK: Body + // ========== + public var body: some View { LazyVGrid(columns: columns, spacing: ODSSpacing.none) { ForEach(cardModels) { cardModel in diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/ODSListOfCardImageFirst.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/ODSListOfCardImageFirst.swift deleted file mode 100644 index b1edfdf9..00000000 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/ODSListOfCardImageFirst.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// MIT License -// Copyright (c) 2021 Orange -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the Software), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -// - -import SwiftUI - -public struct ODSListOfCardImageFirstItemModel: Identifiable { - let cardModel: ODSCardVerticalImageFirstModel - let destination: AnyView? - - public init(cardModel: ODSCardVerticalImageFirstModel, @ViewBuilder destination: () -> Destination) where Destination: View { - self.cardModel = cardModel - self.destination = AnyView(destination()) - } - - public init(cardModel: ODSCardVerticalImageFirstModel) { - self.cardModel = cardModel - destination = AnyView(Text(cardModel.title + " to be define")) - } - - public var id: String { - cardModel.title - } -} - -public struct ODSListOfCardImageFirst: View { - - let title: String - let itemModels: [ODSListOfCardImageFirstItemModel] - - let columns = [ - GridItem(.flexible(), alignment: .topLeading), - ] - - public init(title: String, itemModels: [ODSListOfCardImageFirstItemModel]) { - self.title = title - self.itemModels = itemModels - } - - public var body: some View { - ScrollView { - LazyVGrid(columns: columns, spacing: ODSSpacing.xs) { - ForEach(itemModels) { itemModel in - ODSCardVerticalImageFirstListItem(model: itemModel) - } - } - .padding(EdgeInsets(top: ODSSpacing.m, leading: ODSSpacing.m, bottom: ODSSpacing.m, trailing: ODSSpacing.m)) - } - .navigationTitle(title) - } -} - -struct ODSCardVerticalImageFirstListItem: View { - - let model: ODSListOfCardImageFirstItemModel - - var body: some View { - NavigationLink { - model.destination - .navigationTitle(model.cardModel.title) - } label: { - ODSCardVerticalImageFirst(model: model.cardModel) - } - } -} - -#if DEBUG -struct ODSListOfCardImageFirst_Previews: PreviewProvider { - - static let itemsModels: [ODSListOfCardImageFirstItemModel] = (1 ... 10).map { - let title = "Card \($0)" - let model = ODSCardVerticalImageFirstModel( - title: title, - subtitle: "Subtitle", - imageSource: .image(Image("ods_empty", bundle: Bundle.ods))) - - return ODSListOfCardImageFirstItemModel(cardModel: model) { - Text("This is the \(title) destination view") - } - } - - static var previews: some View { - ForEach(ColorScheme.allCases, id: \.self) { - ODSListOfCardImageFirst(title: "List of Image First cards", itemModels: itemsModels) - .preferredColorScheme($0) - } - } -} -#endif diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/ODSComponentsColors.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/ODSComponentsColors.swift index 8c4dda5c..2405a2bb 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/ODSComponentsColors.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/ODSComponentsColors.swift @@ -47,7 +47,7 @@ public struct ODSComponentColors { public var toolBarItem: Color // Buttons - public var highestEmphasisText: Color + public var highEmphasisText: Color public var functionalPositive: Color public var functionalNegative: Color public var functionalAlert: Color @@ -78,7 +78,7 @@ public struct ODSComponentColors { self.toolBarItem = .green // Buttons - self.highestEmphasisText = Color(UIColor.systemBackground) + self.highEmphasisText = Color(UIColor.systemBackground) self.functionalNegative = .red self.functionalPositive = .green self.functionalInfo = .blue diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcodeproj/project.pbxproj b/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcodeproj/project.pbxproj index 1f09e1ee..e768e02e 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcodeproj/project.pbxproj +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 075326802AD02E1D003D8832 /* ListOfVerticalImageFirstCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0753267F2AD02E1D003D8832 /* ListOfVerticalImageFirstCard.swift */; }; + 075326822AD03615003D8832 /* GrifOfSmallCards.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075326812AD03615003D8832 /* GrifOfSmallCards.swift */; }; 077C37CF2A9DD643003D6B51 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 077C37CA2A9DD643003D6B51 /* Colors.xcassets */; }; 077C37D32A9DD643003D6B51 /* Recipes.json in Resources */ = {isa = PBXBuildFile; fileRef = 077C37CE2A9DD643003D6B51 /* Recipes.json */; }; 077C37D72A9DD6A4003D6B51 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 077C37D62A9DD6A3003D6B51 /* Preview Assets.xcassets */; }; @@ -126,6 +128,8 @@ 07210C482A8FAD1500507988 /* Fastfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Fastfile; path = fastlane/Fastfile; sourceTree = ""; }; 07210C492A8FAD1600507988 /* Appfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Appfile; path = fastlane/Appfile; sourceTree = ""; }; 07210C4C2A8FAD5900507988 /* Pluginfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Pluginfile; path = fastlane/Pluginfile; sourceTree = ""; }; + 0753267F2AD02E1D003D8832 /* ListOfVerticalImageFirstCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOfVerticalImageFirstCard.swift; sourceTree = ""; }; + 075326812AD03615003D8832 /* GrifOfSmallCards.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrifOfSmallCards.swift; sourceTree = ""; }; 077C37C12A9CED34003D6B51 /* Gemfile */ = {isa = PBXFileReference; lastKnownFileType = text; name = Gemfile; path = ../Gemfile; sourceTree = ""; }; 077C37C22A9CED5E003D6B51 /* .gitattributes */ = {isa = PBXFileReference; lastKnownFileType = text; name = .gitattributes; path = ../.gitattributes; sourceTree = ""; }; 077C37C32A9CED5E003D6B51 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; name = .swiftlint.yml; path = ../.swiftlint.yml; sourceTree = ""; }; @@ -214,6 +218,8 @@ 077C38952A9DEEDC003D6B51 /* ThemeSelectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeSelectionView.swift; sourceTree = ""; }; 077C38962A9DEEDC003D6B51 /* ThemeablePreviews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeablePreviews.swift; sourceTree = ""; }; 07AA3D4D28AE8B160001B75E /* Pods_OrangeDesignSystemDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_OrangeDesignSystemDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 07B030592AB0B454001764E7 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 07B0305A2AB0B4A9001764E7 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = CONTRIBUTING.md; path = ../CONTRIBUTING.md; sourceTree = ""; }; 2ACFE972C59B1460F410852D /* Pods-OrangeDesignSystemDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OrangeDesignSystemDemo.debug.xcconfig"; path = "Target Support Files/Pods-OrangeDesignSystemDemo/Pods-OrangeDesignSystemDemo.debug.xcconfig"; sourceTree = ""; }; 759CBF84E701D1BE3D68D365 /* Pods_OrangeDesignSystemDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OrangeDesignSystemDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B655A0B148F11BB69BCFF556 /* Pods-OrangeDesignSystemDemoTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OrangeDesignSystemDemoTests.debug.xcconfig"; path = "Target Support Files/Pods-OrangeDesignSystemDemoTests/Pods-OrangeDesignSystemDemoTests.debug.xcconfig"; sourceTree = ""; }; @@ -272,6 +278,7 @@ 07210C422A8FAC6500507988 /* README.md */, 07210C402A8FAC6500507988 /* THIRD-PARTY.md */, FDDAB0F62809AB2100ACE5F4 /* CHANGELOG.md */, + 07B0305A2AB0B4A9001764E7 /* CONTRIBUTING.md */, ); name = "🛠"; sourceTree = ""; @@ -286,6 +293,16 @@ name = Fastlane; sourceTree = ""; }; + 0753267E2AD00137003D8832 /* CardListAndCollection */ = { + isa = PBXGroup; + children = ( + 077C38472A9DDC79003D6B51 /* CardViewDemo.swift */, + 0753267F2AD02E1D003D8832 /* ListOfVerticalImageFirstCard.swift */, + 075326812AD03615003D8832 /* GrifOfSmallCards.swift */, + ); + path = CardListAndCollection; + sourceTree = ""; + }; 077C37C02A9CECFF003D6B51 /* Configuration */ = { isa = PBXGroup; children = ( @@ -607,9 +624,9 @@ 077C38402A9DDC79003D6B51 /* Modules */ = { isa = PBXGroup; children = ( + 0753267E2AD00137003D8832 /* CardListAndCollection */, 077C38412A9DDC79003D6B51 /* About */, 077C38462A9DDC79003D6B51 /* ModulesList.swift */, - 077C38472A9DDC79003D6B51 /* CardViewDemo.swift */, ); path = Modules; sourceTree = ""; @@ -744,6 +761,7 @@ 077C37C92A9DD5FF003D6B51 /* Resources */, F99FF06E2767AE2A006236A0 /* OrangeDesignSystemApp.swift */, 077C37E52A9DDC1A003D6B51 /* MainTabView.swift */, + 07B030592AB0B454001764E7 /* README.md */, ); name = OrangeDesignSystemDemo; sourceTree = ""; @@ -1070,6 +1088,7 @@ 077C386D2A9DDC79003D6B51 /* BannerVariantOptions.swift in Sources */, 077C38682A9DDC79003D6B51 /* TabBarVariant.swift in Sources */, 077C384E2A9DDC79003D6B51 /* ButtonsComponent.swift in Sources */, + 075326802AD02E1D003D8832 /* ListOfVerticalImageFirstCard.swift in Sources */, 077C38792A9DDC79003D6B51 /* RGBA.swift in Sources */, 077C386C2A9DDC79003D6B51 /* NavigationBarComponent.swift in Sources */, 077C38602A9DDC79003D6B51 /* CardVerticalHeaderFirstVariant.swift in Sources */, @@ -1088,6 +1107,7 @@ 077C38562A9DDC79003D6B51 /* SelectionListOptions.swift in Sources */, 077C387F2A9DDC79003D6B51 /* TypographyGuideline.swift in Sources */, 077C389C2A9DEEDC003D6B51 /* RecipesLoader.swift in Sources */, + 075326822AD03615003D8832 /* GrifOfSmallCards.swift in Sources */, 077C38742A9DDC79003D6B51 /* SliderComponent.swift in Sources */, 077C389A2A9DEEDC003D6B51 /* RecipeBookModel.swift in Sources */, F99FF0732767AE2A006236A0 /* OrangeDesignSystemApp.swift in Sources */, @@ -1271,7 +1291,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.13.1; + MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = "soft.cocoa.ods-ios-demo.dev"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1306,7 +1326,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.13.1; + MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = "soft.cocoa.ods-ios-demo.dev"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/AppNews.json b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/AppNews.json index c0aa2b8f..bc3e9842 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/AppNews.json +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/AppNews.json @@ -1,8 +1,13 @@ [ + { + "version": "0.14.0", + "date": "2023-10-09", + "news": "Update the API of Cards and Banner components to use SwiftUI elements.\nUpdate button emphasis to be aligned to the web design system.\nFix some bugs like the disable state of the IconButton." + }, { "version": "0.13.1", "date": "2023-09-13", - "news": "Update AppNews" + "news": "Update AppNews file" }, { "version": "0.13.0", diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/About/AboutSceen.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/About/AboutSceen.swift index 2c0bf0cc..ae777bed 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/About/AboutSceen.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/About/AboutSceen.swift @@ -96,7 +96,7 @@ struct AboutScreen: View { shareConfiguration: ODSAboutShareTheApplication( storeUrl: storeUrl, subject: "The Orange Design System", - description: "You will find the Orange Design System Mobile App that provides examples of design implementations at: \(storeUrl.absoluteString)"), + description: "Here, you will find the Orange Design System Mobile App that provides examples of design implementations"), onFeedbackClicked: { UIApplication.shared.open(URL(string: "https://github.com/Orange-OpenSource/ods-ios/issues/new/choose")!) }) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/ComponentList.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/ComponentList.swift index 40dd64e3..bd71fc25 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/ComponentList.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/ComponentList.swift @@ -31,12 +31,8 @@ struct ComponentsList: View { // MARK: Stored Properties // ======================= + @EnvironmentObject private var themeProvider: ThemeProvider let components: [Component] - let columns = [ - GridItem(.adaptive(minimum: 150), spacing: ODSSpacing.m, alignment: .topLeading), - ] - - @EnvironmentObject var themeProvider: ThemeProvider // ================= // MARK: Initializer diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Banners/BannerComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Banners/BannerComponent.swift index 5198c754..047c00b7 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Banners/BannerComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Banners/BannerComponent.swift @@ -65,7 +65,6 @@ struct BannerVariant: View { CustomizableVariant { BannerVariantContent(model: model) } options: { - BannerVariantOptions(model: model) } } @@ -85,21 +84,28 @@ struct BannerVariantContent: View { var body: some View { VStack { - Group { - switch model.buttonsOption { - case .none: - ODSBanner(text: model.text, image: model.image) - case .oneButtonNextToText: - ODSBanner(text: model.text, image: model.image, - button: model.button, position: .trailing) - case .oneButtonUnderText: - ODSBanner(text: model.text, image: model.image, - button: model.button, position: .bottom) - case .twoButtons: - ODSBanner(text: model.text, image: model.image, - leadingButton: model.leadingButton, - trailingButton: model.trailingButton) + switch model.buttonCount { + case 0: + ODSBanner(model.text, imageSource: model.imageSource) + case 1: + ODSBanner(model.text, imageSource: model.imageSource) { + Button(model.buttonText) { + // Do something + } + } + case 2: + ODSBanner(model.text, imageSource: model.imageSource) { + Button(model.firstButtonText) { + // Do something + } + } secondButton: { + Button(model.secondButtonText) { + // Do something + } } + + default: + EmptyView() } Spacer() diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Banners/BannerVariantOptions.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Banners/BannerVariantOptions.swift index ca8d02d7..61069e04 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Banners/BannerVariantOptions.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Banners/BannerVariantOptions.swift @@ -32,35 +32,9 @@ class BannerVariantModel: ObservableObject { @Published var showLongText: Bool @Published var showImage: Bool - @Published var buttonsOption: ButtonsOption + @Published var buttonCount: Int - enum ButtonsOption: Int, CaseIterable { - case none = 0 - case oneButtonNextToText - case oneButtonUnderText - case twoButtons - - var description: String { - switch self { - case .none: - return "None" - case .oneButtonNextToText: - return "One next to text" - case .oneButtonUnderText: - return "One under text" - case .twoButtons: - return "Two" - } - } - - var chip: ODSChip { - ODSChip(self, text: self.description) - } - - static var chips: [ODSChip] { - Self.allCases.map { $0.chip } - } - } + let buttonsText = ["Action 1", "Action 2"] // ================= // MARK: Initializer @@ -69,40 +43,44 @@ class BannerVariantModel: ObservableObject { init() { self.showLongText = true self.showImage = true - self.buttonsOption = .none + self.buttonCount = 0 } // ============= // MARK: Helpers // ============= - var text: LocalizedStringKey { - let longText = "Two lines text string with two actions. One to two lines is preferable on mobile." - let shortText = "One line text string with one action." + var text: Text { + let longText = "Text could be on several lines. But, One to two lines is preferable on mobile." + let shortText = "Short text" - return LocalizedStringKey(showLongText ? longText : shortText) + return Text(showLongText ? longText : shortText) } - var image: Image? { - showImage ? Image("ods_empty", bundle: Bundle.ods) : nil + var imageSource: ODSImage.Source? { + let placeholder = Image("ods_empty", bundle: Bundle.ods) + + if let url = RecipeBook.shared.recipes.first?.url { + return showImage ? .asyncImage(url, placeholder) : nil + } else { + return showImage ? .image(placeholder) : nil + } } - var button: ODSButton { - ODSButton(text: "Button", emphasis: .low) { - // do something - } + var buttonText: String { + "Action" } - var trailingButton: ODSButton { - ODSButton(text: "Button 1", emphasis: .low) { - // do something - } + var firstButtonText: String { + buttonsText[0] } - var leadingButton: ODSButton { - ODSButton(text: "Button 2", emphasis: .low) { - // do something - } + var secondButtonText: String { + buttonsText[1] + } + + var numberOfButtons: Int { + buttonsText.count } } @@ -120,18 +98,14 @@ struct BannerVariantOptions: View { var body: some View { VStack(spacing: ODSSpacing.m) { - Group { - Toggle("Long Text", isOn: $model.showLongText) - Toggle("Image", isOn: $model.showImage) - } - .padding(.horizontal, ODSSpacing.m) - .odsFont(.bodyBold) - - ODSChipPicker(title: "Button options", - selection: $model.buttonsOption, - chips: BannerVariantModel.ButtonsOption.chips) + Toggle("Long Text", isOn: $model.showLongText) + Toggle("Image", isOn: $model.showImage) + Stepper("Number of buttons: \(model.buttonCount)", + value: $model.buttonCount, + in: 0 ... model.buttonsText.count) } .odsFont(.bodyRegular) + .padding(.horizontal, ODSSpacing.m) .padding(.vertical, ODSSpacing.m) } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariant.swift index 8a03e61c..c5685260 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariant.swift @@ -162,17 +162,14 @@ fileprivate struct PageContent: View { @ViewBuilder private func exemplePage() -> some View { if let recipe = model.selectedRecipe { - let cardModel = - ODSCardVerticalImageFirstModel(title: recipe.title, - subtitle: recipe.subtitle, - imageSource: .asyncImage(recipe.url, Image("ods_empty", bundle: Bundle.ods)), - supportingText: recipe.description) - ODSCardVerticalImageFirst(model: cardModel) { - ODSButton(text: "Start preparing", emphasis: .highest, action: {}) - } + ODSCardVerticalImageFirst( + title: Text(recipe.title), + imageSource: .asyncImage(recipe.url, Image("ods_empty", bundle: Bundle.ods)), + subtitle: Text(recipe.subtitle), + text: Text(recipe.description)) { + Button("Start preparing") {} + } .padding(.horizontal, ODSSpacing.s) - } else { - EmptyView() } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariantOptions.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariantOptions.swift index 66c234d7..c00e45a3 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariantOptions.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariantOptions.swift @@ -51,6 +51,7 @@ class BottomSheetVariantModel: ObservableObject { self.showSubtitle = false self.showIcon = false self.contentType = .tutorial + self.selectedRecipe = RecipeBook.shared.recipes[0] } @@ -126,7 +127,7 @@ struct ExpandingBottomSheetVariantHome: View { ExpandingBottomSheetVariantOptions(model: model) - ODSButton(text: "See the component", emphasis: .highest, variableWidth: false) { + ODSButton(text: Text("See the component"), emphasis: .high, fullWidth: true) { showBottomSheet = true } .multilineTextAlignment(.center) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/Standard/BottomSheetStandardVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/Standard/BottomSheetStandardVariant.swift index a583103c..128b2bb0 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/Standard/BottomSheetStandardVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/Standard/BottomSheetStandardVariant.swift @@ -51,13 +51,14 @@ struct StandardBottomSheetVariant: View { @ViewBuilder private var pageContent: some View { if let recipe = selectedRecipe { - let cardModel = ODSCardVerticalImageFirstModel(title: recipe.title, - subtitle: recipe.subtitle, - imageSource: .asyncImage(recipe.url, Image("ods_empty", bundle: Bundle.ods)), - supportingText: recipe.description) - ScrollView { - ODSCardVerticalImageFirst(model: cardModel).padding(.horizontal, ODSSpacing.s) + ODSCardVerticalImageFirst( + title: Text(recipe.title), + imageSource: .asyncImage(recipe.url, Image("ods_empty", bundle: Bundle.ods)), + subtitle: Text(recipe.subtitle), + text: Text(recipe.description)) { + Button("Start preparing") {} + } } } else { EmptyView() diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/EmphasisAndFunctionnalVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/EmphasisAndFunctionnalVariant.swift index f98785fd..5156ee30 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/EmphasisAndFunctionnalVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/EmphasisAndFunctionnalVariant.swift @@ -51,11 +51,11 @@ struct EmphasisVariant: View { } .accessibilityAddTraits(.isHeader) - ODSButton(text: model.text, + ODSButton(text: Text(model.text), image: model.icon, emphasis: emphasis, - variableWidth: model.showVariableWidth) {} - .disabled(model.showDisabled) + fullWidth: model.showFullWidth) {} + .disabled(!model.showEnabled) .accessibilityLabel("\(emphasis.rawValue) emphasis button") } } @@ -88,11 +88,11 @@ struct FunctionalVariant: View { } .accessibilityAddTraits(.isHeader) - ODSFunctionalButton(text: model.text, + ODSFunctionalButton(text: Text(model.text), image: model.icon, style: style, - variableWidth: model.showVariableWidth) {} - .disabled(model.showDisabled) + fullWidth: model.showFullWidth) {} + .disabled(!model.showEnabled) .accessibilityLabel("\(style.rawValue) functional button") } } @@ -119,9 +119,9 @@ class EmphasisAndFunctionnalVariantModel: ObservableObject { // ======================= @Published var showIcon: Bool - @Published var showVariableWidth: Bool + @Published var showFullWidth: Bool @Published var showLongText: Bool - @Published var showDisabled: Bool + @Published var showEnabled: Bool // ================= // MARK: Initializer @@ -129,9 +129,9 @@ class EmphasisAndFunctionnalVariantModel: ObservableObject { init() { showIcon = false - showVariableWidth = false + showFullWidth = false showLongText = false - showDisabled = false + showEnabled = true } // ===================== @@ -139,7 +139,7 @@ class EmphasisAndFunctionnalVariantModel: ObservableObject { // ===================== var text: LocalizedStringKey { - showLongText ? "Terms and conditions" : (showDisabled ? "Disabled" : "Enabled") + showLongText ? "Terms and conditions" : (showEnabled ? "Enabled" : "Disabled") } var icon: Image? { @@ -161,10 +161,10 @@ struct EmphasisAndFunctionalVariantOptions: View { var body: some View { VStack { - Toggle("Show icon", isOn: $model.showIcon) - Toggle("Show variable width", isOn: $model.showVariableWidth) - Toggle("Show disabled", isOn: $model.showDisabled) - Toggle("Show long text", isOn: $model.showLongText) + Toggle("Icon", isOn: $model.showIcon) + Toggle("Full width", isOn: $model.showFullWidth) + Toggle("Enabled", isOn: $model.showEnabled) + Toggle("Long text", isOn: $model.showLongText) } .padding(.horizontal, ODSSpacing.m) .padding(.vertical, ODSSpacing.s) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/IconVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/IconVariant.swift index b3de8c5f..3c27ab24 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/IconVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/IconVariant.swift @@ -60,14 +60,14 @@ struct IconVariant: View { Text("Icon (add)").odsFont(.headline).frame(maxWidth: .infinity, alignment: .leading) ODSIconButton(image: Image("Add")) {} - .disabled(model.showDisabled) + .disabled(!model.showEnabled) } VStack(alignment: .center, spacing: ODSSpacing.s) { Text("Icon (info)").odsFont(.headline).frame(maxWidth: .infinity, alignment: .leading) ODSIconButton(image: Image(systemName: "info.circle")) {} - .disabled(model.showDisabled) + .disabled(!model.showEnabled) } } } @@ -85,14 +85,14 @@ class IconVariantModel: ObservableObject { // MARK: Stored properties // ======================= - @Published var showDisabled: Bool + @Published var showEnabled: Bool // ================= // MARK: Initializer // ================= init() { - showDisabled = false + showEnabled = true } } @@ -111,7 +111,7 @@ struct IconVariantOptions: View { var body: some View { VStack { - Toggle("Show disabled", isOn: $model.showDisabled) + Toggle("Enabled", isOn: $model.showEnabled) } .padding(.horizontal, ODSSpacing.m) .padding(.vertical, ODSSpacing.s) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardHorizontalVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardHorizontalVariant.swift index a1509515..7b5f7fae 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardHorizontalVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardHorizontalVariant.swift @@ -24,8 +24,9 @@ import OrangeDesignSystem import SwiftUI -extension ODSCardHorizontalModel.ImagePosition: CaseIterable { - public static var allCases: [ODSCardHorizontalModel.ImagePosition] = [.leading, .trailing] +extension ODSCardHorizontal.ImagePosition: CaseIterable { + + public static var allCases: [ODSCardHorizontal.ImagePosition] = [.leading, .trailing] var description: String { switch self { @@ -52,17 +53,15 @@ class CardHorizontalVariantModel: ObservableObject { // ======================= @Published var showSubtitle: Bool - @Published var showSupportingText: Bool + @Published var showText: Bool @Published var buttonCount: Int @Published var showAlert: Bool - @Published var imagePosition: ODSCardHorizontalModel.ImagePosition + @Published var imagePosition: ODSCardHorizontal.ImagePosition @Published var showDivider: Bool var alertText: String = "" - private let buttonsText = ["Button 1", "Button 2"] - private var recipe: Recipe { - RecipeBook.shared.recipes[0] - } + let buttonsText: [String] + private let recipe: Recipe // ================= // MARK: Initializer @@ -70,40 +69,49 @@ class CardHorizontalVariantModel: ObservableObject { init() { showSubtitle = true - showSupportingText = true + showText = true imagePosition = .leading buttonCount = 0 showAlert = false showDivider = true + + buttonsText = ["Button 1", "Button 2"] + recipe = RecipeBook.shared.recipes[0] } - // ============= - // MARK: Helpers - // ============= - - var cardModel: ODSCardHorizontalModel { - ODSCardHorizontalModel( - title: recipe.title, - subtitle: showSubtitle ? recipe.subtitle : nil, - imageSource: .asyncImage(recipe.url, Image("ods_empty", bundle: Bundle.ods)), - imagePosition: imagePosition, - supportingText: showSupportingText ? recipe.description : nil, - dividerEnabled: showDivider) + // ================== + // MARK: Card Content + // ================== + + var title: Text { + Text(recipe.title) } - - func displayAlert(text: String) { - self.alertText = text - self.showAlert = true + + var subtitle: Text? { + showSubtitle ? Text(recipe.subtitle) : nil } - var button1Text: String? { - buttonCount >= 1 ? buttonsText[0] : nil + var imageSource: ODSImage.Source { + .asyncImage(recipe.url, Image("ods_empty", bundle: Bundle.ods)) + } + + var text: Text? { + showText ? Text(recipe.description) : nil + } + + var firstButtonText: String { + buttonsText[0] } - var button2Text: String? { - buttonCount >= 2 ? buttonsText[1] : nil + var secondButtonText: String { + buttonsText[1] } + func displayAlert(text: String) { + self.alertText = text + self.showAlert = true + } + var numberOfButtons: Int { buttonsText.count } @@ -124,24 +132,12 @@ struct CardHorizontalVariant: View { var body: some View { CustomizableVariant { ScrollView { - ODSCardHorizontal(model: model.cardModel) { - if let text = model.button1Text { - ODSButton(text: LocalizedStringKey(text), emphasis: .medium) { - model.displayAlert(text: "Button 1 clicked") - } + card + .padding(.horizontal, ODSSpacing.m) + .padding(.top, ODSSpacing.m) + .onTapGesture { + model.displayAlert(text: "Card container clicked") } - } buttonContent2: { - if let text = model.button2Text { - ODSButton(text: LocalizedStringKey(text), emphasis: .medium) { - model.displayAlert(text: "Button 2 clicked") - } - } - } - .padding(.horizontal, ODSSpacing.m) - .padding(.top, ODSSpacing.m) - .onTapGesture { - model.displayAlert(text: "Card container clicked") - } } .alert(model.alertText, isPresented: $model.showAlert) { Button("close", role: .cancel) {} @@ -150,6 +146,51 @@ struct CardHorizontalVariant: View { CardHorizontalVariantOptions(model: model) } } + + @ViewBuilder + private var card: some View { + switch model.buttonCount { + case 0: + ODSCardHorizontal(title: model.title, + imageSource: model.imageSource, + imagePosition: model.imagePosition, + subtitle: model.subtitle, + text: model.text) + case 1: + ODSCardHorizontal( + title: model.title, + imageSource: model.imageSource, + imagePosition: model.imagePosition, + subtitle: model.subtitle, + text: model.text, + dividerEnabled: model.showDivider + ) { + Button(model.firstButtonText) { + model.displayAlert(text: "\(model.firstButtonText) clicked") + } + } + case 2: + ODSCardHorizontal( + title: model.title, + imageSource: model.imageSource, + imagePosition: model.imagePosition, + subtitle: model.subtitle, + text: model.text, + dividerEnabled: model.showDivider + ) { + Button(model.firstButtonText) { + model.displayAlert(text: "\(model.firstButtonText) clicked") + } + } secondButton: { + Button(model.secondButtonText) { + model.displayAlert(text: "\(model.secondButtonText) clicked") + } + } + + default: + EmptyView() + } + } } private struct CardHorizontalVariantOptions: View { @@ -168,19 +209,19 @@ private struct CardHorizontalVariantOptions: View { VStack(spacing: ODSSpacing.m) { Toggle("Subtitle", isOn: $model.showSubtitle) .padding(.horizontal, ODSSpacing.m) - Toggle("Text", isOn: $model.showSupportingText) + Toggle("Text", isOn: $model.showText) .padding(.horizontal, ODSSpacing.m) ODSChipPicker(title: "Image position", selection: $model.imagePosition, - chips: ODSCardHorizontalModel.ImagePosition.chips) + chips: ODSCardHorizontal.ImagePosition.chips) Toggle("Divider", isOn: $model.showDivider) .padding(.horizontal, ODSSpacing.m) Stepper("Number of buttons: \(model.buttonCount)", value: $model.buttonCount, - in: 0 ... model.numberOfButtons) + in: 0 ... model.buttonsText.count) .padding(.horizontal, ODSSpacing.m) } .odsFont(.bodyRegular) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardSmallVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardSmallVariant.swift index 4bb18d3d..65f71d46 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardSmallVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardSmallVariant.swift @@ -74,9 +74,9 @@ struct CardSmallVariant: View { } } } + .padding(.horizontal, ODSSpacing.m) + .padding(.top, ODSSpacing.m) } - .padding(.horizontal, ODSSpacing.m) - .padding(.top, ODSSpacing.m) } options: { CardSmallVariantOptions(model: model) } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardVerticalHeaderFirstVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardVerticalHeaderFirstVariant.swift index b6817d76..379a3c67 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardVerticalHeaderFirstVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardVerticalHeaderFirstVariant.swift @@ -32,13 +32,14 @@ class CardVerticalHeaderFirstVariantModel: ObservableObject { @Published var showThumbnail: Bool @Published var showSubtitle: Bool - @Published var showSupportingText: Bool + @Published var showText: Bool @Published var buttonCount: Int @Published var showAlert: Bool + var alertText: String = "" + let buttonsText: [String] + private let recipe: Recipe - private let buttonsText = ["Button 1", "Button 2"] - // ================= // MARK: Initializer // ================= @@ -46,75 +47,77 @@ class CardVerticalHeaderFirstVariantModel: ObservableObject { init() { showThumbnail = true showSubtitle = true - showSupportingText = true + showText = true buttonCount = 2 showAlert = false + + buttonsText = ["Button 1", "Button 2"] + recipe = RecipeBook.shared.recipes[0] } + + // ================== + // MARK: Card Content + // ================== - // ============= - // MARK: Helpers - // ============= - - var cardModel: ODSCardVerticalHeaderFirstModel { - ODSCardVerticalHeaderFirstModel( - title: cardExampleTitle, - subtitle: showSubtitle ? cardExampleSubtitle : nil, - thumbnail: showThumbnail ? Image("ods_empty", bundle: Bundle.ods) : nil, - imageSource: cardExampleImage, - supportingText: showSupportingText ? cardExampleSupportingText : nil) + var title: Text { + Text(recipe.title) } - var button1Text: String? { - buttonCount >= 1 ? buttonsText[0] : nil + var subtitle: Text? { + showSubtitle ? Text(recipe.subtitle) : nil } - - var button2Text: String? { - buttonCount >= 2 ? buttonsText[1] : nil + + var thumbnail: Image? { + showThumbnail ? Image("ods_empty", bundle: Bundle.ods) : nil } - - var numberOfButtons: Int { - buttonsText.count + + var imageSource: ODSImage.Source { + .asyncImage(recipe.url, Image("ods_empty", bundle: Bundle.ods)) + } + + var text: Text? { + showText ? Text(recipe.description) : nil + } + + var firstButtonText: String { + buttonsText[0] + } + + var secondButtonText: String { + buttonsText[1] } func displayAlert(text: String) { self.alertText = text self.showAlert = true } + + var numberOfButtons: Int { + buttonsText.count + } } struct CardVerticalHeaderFirstVariant: View { - + // ======================= // MARK: Stored properties // ======================= - + @ObservedObject var model: CardVerticalHeaderFirstVariantModel - + // ========== // MARK: Body // ========== - + var body: some View { CustomizableVariant { ScrollView { - ODSCardVerticalHeaderFirst(model: model.cardModel) { - if let text = model.button1Text { - ODSButton(text: LocalizedStringKey(text), emphasis: .medium) { - model.displayAlert(text: "Button 1 clicked") - } - } - } buttonContent2: { - if let text = model.button2Text { - ODSButton(text: LocalizedStringKey(text), emphasis: .medium) { - model.displayAlert(text: "Button 2 clicked") - } + card + .padding(.horizontal, ODSSpacing.m) + .padding(.top, ODSSpacing.m) + .onTapGesture { + model.displayAlert(text: "Card container clicked") } - } - .padding(.horizontal, ODSSpacing.m) - .padding(.top, ODSSpacing.m) - .onTapGesture { - model.displayAlert(text: "Card container clicked") - } } .alert(model.alertText, isPresented: $model.showAlert) { Button("close", role: .cancel) {} @@ -123,6 +126,49 @@ struct CardVerticalHeaderFirstVariant: View { CardVerticalHeaderFirstVariantOptions(model: model) } } + + @ViewBuilder + private var card: some View { + switch model.buttonCount { + case 0: + ODSCardVerticalHeaderFirst(title: model.title, + imageSource: model.imageSource, + subtitle: model.subtitle, + thumbnail: model.thumbnail, + text: model.text) + case 1: + ODSCardVerticalHeaderFirst( + title: model.title, + imageSource: model.imageSource, + subtitle: model.subtitle, + thumbnail: model.thumbnail, + text: model.text + ) { + Button(model.firstButtonText) { + model.displayAlert(text: "\(model.firstButtonText) clicked") + } + } + case 2: + ODSCardVerticalHeaderFirst( + title: model.title, + imageSource: model.imageSource, + subtitle: model.subtitle, + thumbnail: model.thumbnail, + text: model.text + ) { + Button(model.firstButtonText) { + model.displayAlert(text: "\(model.firstButtonText) clicked") + } + } secondButton: { + Button(model.secondButtonText) { + model.displayAlert(text: "\(model.secondButtonText) clicked") + } + } + + default: + EmptyView() + } + } } private struct CardVerticalHeaderFirstVariantOptions: View { @@ -141,11 +187,11 @@ private struct CardVerticalHeaderFirstVariantOptions: View { VStack(spacing: ODSSpacing.m) { Toggle("Thumbnail", isOn: $model.showThumbnail) Toggle("Subtitle", isOn: $model.showSubtitle) - Toggle("Text", isOn: $model.showSupportingText) + Toggle("Text", isOn: $model.showText) Stepper("Number of buttons: \(model.buttonCount)", value: $model.buttonCount, - in: 0 ... model.numberOfButtons) + in: 0 ... model.buttonsText.count) } .odsFont(.bodyRegular) .padding(.vertical, ODSSpacing.m) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardVerticalImageFirstVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardVerticalImageFirstVariant.swift index 8f10d2ff..653dc32f 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardVerticalImageFirstVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardVerticalImageFirstVariant.swift @@ -31,11 +31,13 @@ class CardVerticalImageFirstVariantModel: ObservableObject { // ======================= @Published var showSubtitle: Bool - @Published var showSupportingText: Bool + @Published var showText: Bool @Published var buttonCount: Int @Published var showAlert: Bool + var alertText: String = "" - private let buttonsText = ["Button 1", "Button 2"] + private let buttonsText: [String] + private let recipe: Recipe // ================= // MARK: Initializer @@ -43,33 +45,45 @@ class CardVerticalImageFirstVariantModel: ObservableObject { init() { showSubtitle = true - showSupportingText = true + showText = true buttonCount = 2 showAlert = false + + buttonsText = ["Button 1", "Button 2"] + recipe = RecipeBook.shared.recipes[0] } // ============= // MARK: Helpers // ============= - var cardModel: ODSCardVerticalImageFirstModel { - ODSCardVerticalImageFirstModel(title: cardExampleTitle, - subtitle: showSubtitle ? cardExampleSubtitle : nil, - imageSource: cardExampleImage, - supportingText: showSupportingText ? cardExampleSupportingText : nil) + var title: Text { + Text(recipe.title) } - func displayAlert(text: String) { - self.alertText = text - self.showAlert = true + var subtitle: Text? { + showSubtitle ? Text(recipe.subtitle) : nil + } + + var imageSource: ODSImage.Source { + .asyncImage(recipe.url, Image("ods_empty", bundle: Bundle.ods)) } - var button1Text: String? { - buttonCount >= 1 ? buttonsText[0] : nil + var text: Text? { + showText ? Text(recipe.description) : nil + } + + var firstButtonText: String { + buttonsText[0] } - var button2Text: String? { - buttonCount >= 2 ? buttonsText[1] : nil + var secondButtonText: String { + buttonsText[1] + } + + func displayAlert(text: String) { + self.alertText = text + self.showAlert = true } var numberOfButtons: Int { @@ -93,24 +107,12 @@ struct CardVerticalImageFirstVariant: View { CustomizableVariant { // Card demonstrator ScrollView { - ODSCardVerticalImageFirst(model: model.cardModel) { - if let text = model.button1Text { - ODSButton(text: LocalizedStringKey(text), emphasis: .medium) { - model.displayAlert(text: "Button 1 clicked") - } - } - } buttonContent2: { - if let text = model.button2Text { - ODSButton(text: LocalizedStringKey(text), emphasis: .medium) { - model.displayAlert(text: "Button 2 clicked") - } + card + .padding(.horizontal, ODSSpacing.m) + .padding(.top, ODSSpacing.m) + .onTapGesture { + model.displayAlert(text: "Card container clicked") } - } - .padding(.horizontal, ODSSpacing.s) - .padding(.top, ODSSpacing.m) - .onTapGesture { - model.displayAlert(text: "Card container clicked") - } } .alert(model.alertText, isPresented: $model.showAlert) { Button("close", role: .cancel) {} @@ -119,6 +121,50 @@ struct CardVerticalImageFirstVariant: View { CardVerticalImageFirstVariantOptions(model: model) } } + + // =================== + // MARK: Pivate Helper + // =================== + + @ViewBuilder + private var card: some View { + switch model.buttonCount { + case 0: + ODSCardVerticalImageFirst( + title: model.title, + imageSource: model.imageSource, + subtitle: model.subtitle, + text: model.text) + case 1: + ODSCardVerticalImageFirst( + title: model.title, + imageSource: model.imageSource, + subtitle: model.subtitle, + text: model.text + ) { + Button(model.firstButtonText) { + model.displayAlert(text: "\(model.firstButtonText) clicked") + } + } + case 2: + ODSCardVerticalImageFirst( + title: model.title, + imageSource: model.imageSource, + subtitle: model.subtitle, + text: model.text + ) { + Button(model.firstButtonText) { + model.displayAlert(text: "\(model.firstButtonText) clicked") + } + } secondButton: { + Button(model.secondButtonText) { + model.displayAlert(text: "\(model.secondButtonText) clicked") + } + } + default: + EmptyView() + } + } } private struct CardVerticalImageFirstVariantOptions: View { @@ -136,7 +182,7 @@ private struct CardVerticalImageFirstVariantOptions: View { var body: some View { VStack(spacing: ODSSpacing.m) { Toggle("Subtitle", isOn: $model.showSubtitle) - Toggle("Text", isOn: $model.showSupportingText) + Toggle("Text", isOn: $model.showText) Stepper("Number of buttons: \(model.buttonCount)", value: $model.buttonCount, diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/SelectionVariant/SelectionList.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/SelectionVariant/SelectionList.swift index bc9b37c2..bdea7d83 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/SelectionVariant/SelectionList.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/SelectionVariant/SelectionList.swift @@ -41,7 +41,7 @@ struct SelectionListVariant: View { CustomizableVariant { SelectionListVariantInner(model: model) } options: { - SelectionListVariantOptions(model: model) + SelectionListVariantOptions(model: model) } } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ToolBar/ToolBarComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ToolBar/ToolBarComponent.swift index 7f41770d..a2866fcd 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ToolBar/ToolBarComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ToolBar/ToolBarComponent.swift @@ -81,7 +81,7 @@ private struct ToolBarVariantHome: View { .padding(.top, ODSSpacing.m) .padding(.bottom, ODSSpacing.xl) - ODSButton(text: "Open sheet", emphasis: .highest, variableWidth: false) { + ODSButton(text: Text("Open sheet"), emphasis: .high, fullWidth: true) { showToolBar = true } .padding(.horizontal, ODSSpacing.m) @@ -126,7 +126,7 @@ private struct ToolBarVariantContent: View { Spacer() - ODSButton(text: "Close sheet", emphasis: .highest, variableWidth: false) { + ODSButton(text: Text("Close sheet"), emphasis: .high, fullWidth: true) { dismiss() } @@ -154,7 +154,7 @@ private struct ToolBarVariantContent: View { private struct ToolBarVariantContentModifier: ViewModifier { // ======================= - // MARK: Stoerd properties + // MARK: Stored properties // ======================= @ObservedObject var model: ToolBarVariantModel diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/GuidelinesList.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/GuidelinesList.swift index 166a938a..0e46cef1 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/GuidelinesList.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/GuidelinesList.swift @@ -31,45 +31,50 @@ struct GuidelinesList: View { // ====================== @EnvironmentObject private var themeProvider: ThemeProvider - let guidelines: [Guideline] + private let guidelines: [Guideline] + private let columns = [GridItem(.flexible(), alignment: .topLeading)] init() { guidelines = [ ColorsGuideline(), SpacingsGuideline(), TypographyGuideline() - ] + ] } - + // ========== // MARK: Body // ========== - + var body: some View { NavigationView { - ODSListOfCardImageFirst(title: "Guidelines", itemModels: itemsModel) + ScrollView { + LazyVGrid(columns: columns, spacing: ODSSpacing.xs) { + ForEach(guidelines, id: \.title) { guideline in + NavigationLink { + GuidelinePage(guideline: guideline) + } label: { + ODSCardVerticalImageFirst( + title: Text(guideline.title), + imageSource: .image(imageFrom(resourceName: guideline.imageName))) + } + } + } + .padding(.all, ODSSpacing.m) .navigationTitle("Guidelines") .navigationbarMenuForThemeSelection() - - GuidelinePage(guideline: guidelines[0]) + } + + GuidelinePage(guideline: guidelines[0]) // Why ? } } - // ============ - // MARK: Helper - // ============ - - private var itemsModel: [ODSListOfCardImageFirstItemModel] { - guidelines.map { itemModel(for: $0) } - } + // ==================== + // MARK: Private helper + // ==================== - private func itemModel(for guideline: Guideline) -> ODSListOfCardImageFirstItemModel { - let image = themeProvider.imageFromResources(guideline.imageName) - let cardModel = ODSCardVerticalImageFirstModel(title: guideline.title, imageSource: .image(image)) - - return ODSListOfCardImageFirstItemModel(cardModel: cardModel) { - GuidelinePage(guideline: guideline) - } + private func imageFrom(resourceName: String) -> Image { + themeProvider.imageFromResources(resourceName) } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/About/AboutModule.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/About/AboutModule.swift index 57c24298..bb49b521 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/About/AboutModule.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/About/AboutModule.swift @@ -93,7 +93,7 @@ struct AboutSetup: View { NavigationLink(isActive: $showDemo) { AboutModuleDemo(model: model) } label: { - ODSButton(text: "View demo", emphasis: .highest, variableWidth: false) { + ODSButton(text: Text("View demo"), emphasis: .high, fullWidth: true) { showDemo.toggle() } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/CardViewDemo.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/CardViewDemo.swift new file mode 100644 index 00000000..816e648e --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/CardViewDemo.swift @@ -0,0 +1,86 @@ +// +// MIT License +// Copyright (c) 2021 Orange +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +import OrangeDesignSystem +import SwiftUI + +struct CardViewDemo: View { + + // ======================= + // MARK: Stored Properties + // ======================= + + @EnvironmentObject private var themeProvider: ThemeProvider + let columns = [GridItem(.flexible(), alignment: .topLeading)] + + // ========== + // MARK: Body + // ========== + + var body: some View { + ScrollView { + LazyVGrid(columns: columns, spacing: ODSSpacing.xs) { + NavigationLink { + ListOfVerticalImageFirstCard() + .navigationTitle("Lits of cards") + .navigationbarMenuForThemeSelection() + } label: { + ODSCardVerticalImageFirst( + title: Text("List of cards"), + imageSource: .image(imageFrom(resourceName: "Cards"))) + } + + NavigationLink { + GrifOfSmallCards() + .navigationTitle("Grid of small cards") + .navigationbarMenuForThemeSelection() + } label: { + ODSCardVerticalImageFirst( + title: Text("Grid of small cards"), + imageSource: .image(imageFrom(resourceName: "Cards_1"))) + } + } + .padding(.all, ODSSpacing.m) + } + } + + // ==================== + // MARK: Private helper + // ==================== + + private func imageFrom(resourceName: String) -> Image { + themeProvider.imageFromResources(resourceName) + } +} + +#if DEBUG +struct CardViewDemo_Previews: PreviewProvider { + static var previews: some View { + CardViewDemo() + .previewInterfaceOrientation(.portrait) + + CardViewDemo() + .previewInterfaceOrientation(.landscapeRight) + } +} +#endif diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/GrifOfSmallCards.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/GrifOfSmallCards.swift new file mode 100644 index 00000000..3f5e7e29 --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/GrifOfSmallCards.swift @@ -0,0 +1,73 @@ +//// +// MIT License +// Copyright (c) 2021 Orange +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +import SwiftUI +import OrangeDesignSystem + +struct GrifOfSmallCards: View { + + // ======================= + // MARK: Stored Properties + // ======================= + + private let cardsModels: [ODSCardSmallModel] + + // ================= + // MARK: Initializer + // ================= + + init() { + cardsModels = RecipeBook.shared.recipes.map { recipe in + return ODSCardSmallModel( + title: recipe.title, + imageSource: .asyncImage(recipe.url, Image("empty", bundle: Bundle.main)) + ) + } + } + + // ========== + // MARK: Body + // ========== + + var body: some View { + ScrollView { + ODSGridOfCards(cardModels: cardsModels) + .padding(.all, ODSSpacing.m) + } + } +} + +#if DEBUG +struct GrifOfSmallCard_Previews: PreviewProvider { + static var previews: some View { + GrifOfSmallCards() + .previewInterfaceOrientation(.portrait) + .environment(\.dynamicTypeSize, .accessibility3) + + GrifOfSmallCards() + .previewInterfaceOrientation(.landscapeRight) + .environment(\.dynamicTypeSize, .accessibility3) + + } +} +#endif diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/ListOfVerticalImageFirstCard.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/ListOfVerticalImageFirstCard.swift new file mode 100644 index 00000000..ab975dbc --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/ListOfVerticalImageFirstCard.swift @@ -0,0 +1,77 @@ +//// +// MIT License +// Copyright (c) 2021 Orange +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +import SwiftUI +import OrangeDesignSystem + +struct ListOfVerticalImageFirstCard: View { + + // ======================= + // MARK: Stored Properties + // ======================= + + private let columns = [GridItem(.flexible(), alignment: .topLeading)] + + // ========== + // MARK: Body + // ========== + + var body: some View { + ScrollView { + LazyVGrid(columns: columns, spacing: ODSSpacing.xs) { + ForEach(RecipeBook.shared.recipes, id: \.title) { recipe in + NavigationLink { + Text("Bon appétit") + .navigationBarTitleDisplayMode(.inline) + .navigationbarMenuForThemeSelection() + } label: { + ODSCardVerticalImageFirst( + title: Text(recipe.title), + imageSource: .asyncImage(recipe.url, Image("ods_empty", bundle: Bundle.ods)), + subtitle: Text(recipe.subtitle), + text: Text(recipe.description) + ) + } + } + } + .padding(.all, ODSSpacing.m) + } + } +} + +#if DEBUG +struct ListOfVerticalImageFirstCard_Previews: PreviewProvider { + static var previews: some View { + ListOfVerticalImageFirstCard() + .previewInterfaceOrientation(.portrait) + + ListOfVerticalImageFirstCard() + .previewInterfaceOrientation(.portrait) + .environment(\.dynamicTypeSize, .accessibility3) + + ListOfVerticalImageFirstCard() + .previewInterfaceOrientation(.landscapeRight) + } +} +#endif + diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardViewDemo.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardViewDemo.swift deleted file mode 100644 index dc34b4a3..00000000 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardViewDemo.swift +++ /dev/null @@ -1,139 +0,0 @@ -// -// MIT License -// Copyright (c) 2021 Orange -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the Software), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -// - -import OrangeDesignSystem -import SwiftUI - -struct CardViewDemo: View { - - // ======================= - // MARK: Stored Properties - // ======================= - - @EnvironmentObject private var themeProvider: ThemeProvider - - // ===================== - // MARK: Computed values - // ===================== - - private func imageFrom(resourceName: String) -> Image { - themeProvider.imageFromResources(resourceName) - } - - private var items: [ODSListOfCardImageFirstItemModel] { - [ - ODSListOfCardImageFirstItemModel(cardModel: ODSCardVerticalImageFirstModel(title: "List", imageSource: .image(imageFrom(resourceName: "Cards")))) { - CardViewDemoList() - }, - ODSListOfCardImageFirstItemModel(cardModel: ODSCardVerticalImageFirstModel(title: "Grid", imageSource: .image(imageFrom(resourceName: "Cards_1")))) { - CardViewDemoGrid() - }, - ] - } - - // ========== - // MARK: Body - // ========== - - var body: some View { - ODSListOfCardImageFirst(title: "Card collections", itemModels: items) - .navigationbarMenuForThemeSelection() - } -} - -struct CardViewDemoGrid: View { - let cardsModelsOLD = (1 ... 10).map { - ODSCardSmallModel(title: "Card \($0)", - imageSource: .image(Image("empty", bundle: Bundle.main))) - } - - let cardsModels = RecipeBook.shared.recipes.map { recipe in - return ODSCardSmallModel(title: recipe.title, - imageSource: .asyncImage(recipe.url, Image("empty", bundle: Bundle.main)) - )} - - var body: some View { - ScrollView { - ODSGridOfCards(cardModels: cardsModels) - .padding(.all, ODSSpacing.m) - } - .navigationTitle("Grid") - .navigationbarMenuForThemeSelection() - } -} - -struct CardViewDemoList: View { - let itemModels: [ODSListOfCardImageFirstItemModel] = - RecipeBook.shared.recipes.map { recipe in - let cardModel = ODSCardVerticalImageFirstModel(title: recipe.title, - subtitle: recipe.subtitle, - imageSource: .asyncImage(recipe.url, Image("ods_empty", bundle: Bundle.ods)), - supportingText: recipe.description) - - return ODSListOfCardImageFirstItemModel(cardModel: cardModel) - } - - let itemModelsOld: [ODSListOfCardImageFirstItemModel] = (1 ... 5).map { - let cardModel = ODSCardVerticalImageFirstModel(title: "Card \($0)", subtitle: "SubTitle \($0)", imageSource: .image(Image("empty")), supportingText: "Description \($0)") - return ODSListOfCardImageFirstItemModel(cardModel: cardModel) - } - - var body: some View { - ODSListOfCardImageFirst(title: "List", itemModels: itemModels) - .navigationbarMenuForThemeSelection() - } -} - -#if DEBUG -struct CardViewDemoGrid_Previews: PreviewProvider { - static var previews: some View { - // - CardViewDemoGrid() - .previewInterfaceOrientation(.portrait) - - CardViewDemoGrid() - .previewInterfaceOrientation(.portrait) - .environment(\.dynamicTypeSize, .accessibility3) // <- CONSTANT - } -} - -struct CardViewDemo_Previews: PreviewProvider { - static var previews: some View { - // - CardViewDemo() - .previewInterfaceOrientation(.portrait) - } -} - -struct CardViewDemoList_Previews: PreviewProvider { - static var previews: some View { - // - CardViewDemoList() - .previewInterfaceOrientation(.portrait) - CardViewDemoList() - .previewInterfaceOrientation(.portrait) - .environment(\.dynamicTypeSize, .accessibility3) // <- CONSTANT - } -} - -#endif diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/ModulesList.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/ModulesList.swift index 74f4aa3c..abb88d47 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/ModulesList.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/ModulesList.swift @@ -31,33 +31,49 @@ struct ModulesList: View { // ======================= @EnvironmentObject private var themeProvider: ThemeProvider + private let columns = [GridItem(.flexible(), alignment: .topLeading)] - private func imageFrom(resourceName: String) -> Image { - themeProvider.imageFromResources(resourceName) - } - // ========== // MARK: Body // ========== var body: some View { - let items = [ - ODSListOfCardImageFirstItemModel(cardModel: ODSCardVerticalImageFirstModel(title: "About", imageSource: .image(imageFrom(resourceName: "AboutImage")))) { - AboutModule() - .navigationBarTitleDisplayMode(.inline) - .navigationbarMenuForThemeSelection() - }, + NavigationView { + ScrollView { + LazyVGrid(columns: columns, spacing: ODSSpacing.xs) { + NavigationLink { + AboutModule() + .navigationTitle("About: Setup") + .navigationbarMenuForThemeSelection() + } label: { + ODSCardVerticalImageFirst( + title: Text("About"), + imageSource: .image(imageFrom(resourceName: "AboutImage"))) + } + + NavigationLink { + CardViewDemo() + .navigationTitle("Card collections") + .navigationbarMenuForThemeSelection() + } label: { + ODSCardVerticalImageFirst( + title: Text("Card collections"), + imageSource: .image(imageFrom(resourceName: "Cards"))) + } + } + .padding(.all, ODSSpacing.m) + } + .navigationTitle("Modules") + .navigationbarMenuForThemeSelection() + } + } - ODSListOfCardImageFirstItemModel(cardModel: ODSCardVerticalImageFirstModel(title: "Card collections", imageSource: .image(imageFrom(resourceName: "Cards")))) { - CardViewDemo() - }, - ] + // ==================== + // MARK: Private helper + // ==================== - return NavigationView { - ODSListOfCardImageFirst(title: "Modules", itemModels: items) - .navigationTitle("Modules") - .navigationbarMenuForThemeSelection() - } + private func imageFrom(resourceName: String) -> Image { + themeProvider.imageFromResources(resourceName) } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariant.swift index 8a03e61c..46be95b2 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariant.swift @@ -168,7 +168,7 @@ fileprivate struct PageContent: View { imageSource: .asyncImage(recipe.url, Image("ods_empty", bundle: Bundle.ods)), supportingText: recipe.description) ODSCardVerticalImageFirst(model: cardModel) { - ODSButton(text: "Start preparing", emphasis: .highest, action: {}) + ODSButton(text: "Start preparing", emphasis: .high, action: {}) } .padding(.horizontal, ODSSpacing.s) } else { diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariantOptions.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariantOptions.swift index 66c234d7..df3e37a4 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariantOptions.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariantOptions.swift @@ -126,7 +126,7 @@ struct ExpandingBottomSheetVariantHome: View { ExpandingBottomSheetVariantOptions(model: model) - ODSButton(text: "See the component", emphasis: .highest, variableWidth: false) { + ODSButton(text: "See the component", emphasis: .high, fullWidth: true) { showBottomSheet = true } .multilineTextAlignment(.center) diff --git a/OrangeDesignSystemDemo/README.md b/OrangeDesignSystemDemo/README.md new file mode 100644 index 00000000..3464c4fa --- /dev/null +++ b/OrangeDesignSystemDemo/README.md @@ -0,0 +1,12 @@ +

OrangeDesignSystem iOS Demo Application

+ +

+ Orange Design System iOS provides Orange iOS components to developers and a demo application. +
+ Visit Orange Design System iOS +
+
+ Report bug + · + Request feature +

diff --git a/OrangeTheme/Sources/OrangeTheme/OrangeTheme.swift b/OrangeTheme/Sources/OrangeTheme/OrangeTheme.swift index 195d426c..c381e7be 100644 --- a/OrangeTheme/Sources/OrangeTheme/OrangeTheme.swift +++ b/OrangeTheme/Sources/OrangeTheme/OrangeTheme.swift @@ -306,7 +306,7 @@ public struct OrangeThemeFactory { theme.componentColors.toolBarItem = theme.componentColors.accent // Buttons - theme.componentColors.highestEmphasisText = .black + theme.componentColors.highEmphasisText = .black theme.componentColors.functionalNegative = OrangeColors.functionalNegative.colorDecription.color theme.componentColors.functionalPositive = OrangeColors.functionalPositive.colorDecription.color theme.componentColors.functionalInfo = OrangeColors.functionalInfo.colorDecription.color diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 00000000..df102642 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "BottomSheet", + "repositoryURL": "https://github.com/lucaszischka/BottomSheet", + "state": { + "branch": null, + "revision": "4c9ef84552712e0117c37d4893270fdc28fb9288", + "version": "3.1.0" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift index 9bbfc2e7..d061b2c0 100644 --- a/Package.swift +++ b/Package.swift @@ -62,5 +62,6 @@ let package = Package( name: "OrangeDesignSystemTests", dependencies: ["OrangeDesignSystem"], path: "OrangeDesignSystem/Tests"), - ] + ], + swiftLanguageVersions: [.v5] ) diff --git a/README.md b/README.md index e84c01bf..f01b9bbd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -

ODS iOS

+

Orange Design System iOS

- Orange Design System iOS provides Orange iOS components to developers and a demo application. + Orange Design System iOS provides Orange iOS components to developers and a demo application.
Visit Orange Design System iOS
@@ -11,3 +11,37 @@ Request feature

+[![version 0.13.0](https://img.shields.io/badge/version-0.13.0-brightgreen.svg)](CHANGELOG.md) +[![iOS 15.0](https://img.shields.io/badge/iOS-15.0-informational.svg)](https://developer.apple.com/support/app-store "iOS 15 supports") +[![Swift 5.8](https://img.shields.io/badge/Swift-5.8-informational.svg)](https://swift.org) +[![Xcode 14.3](https://img.shields.io/badge/Xcode-14.3-informational.svg)](https://developer.apple.com/xcode) + +## Table of contents + +- [Content](#content) +- [Bugs and feature requests](#bugs-and-feature-requests) +- [Contributing](#contributing) +- [Copyright and license](#copyright-and-license) + +## Content + +This repository contains the Orange Design System IOS library that provides Orange iOS components, but also a demo application showcasing these different components. + +For more specific information: + +* [Library README](https://github.com/Orange-OpenSource/ods-ios/blob/qualif/OrangeDesignSystem/README.md) +* [Orange Theme README](https://github.com/Orange-OpenSource/ods-ios/blob/qualif/OrangeTheme/README.md) +* [Innovation Cup Theme README](https://github.com/Orange-OpenSource/ods-ios/blob/qualif/InnovationCupTheme/README.md) +* [Demo application README](https://github.com/Orange-OpenSource/ods-ios/blob/qualif/OrangeDesignSystemDemo/README.md) + +## Bugs and feature requests + +Have a bug or a feature request? Please first search for existing and closed issues. If your problem or idea is not addressed yet, [please open a new issue](https://github.com/Orange-OpenSource/ods-ios/issues/new/choose). + +## Contributing + +Please read through our [contributing guidelines](https://github.com/Orange-OpenSource/ods-ios/blob/qualif/CONTRIBUTING.md). Included are directions for opening issues, coding standards, and notes on development. + +## Copyright and license + +Code released under the [MIT License](https://github.com/Orange-OpenSource/ods-ios/blob/qualif/LICENSE). diff --git a/docs/components/banners.md b/docs/components/banners.md index 80b7b446..275b6e9a 100644 --- a/docs/components/banners.md +++ b/docs/components/banners.md @@ -14,9 +14,18 @@ description: A banner displays an important message which requires an action to * [No button](#no-button) * [One button](#on-button) * [Two buttons](#two-buttons) + * [Without image](#without-image) + * [With image from url](#with-image-from-url) --- +A banner displays an important, succinct message, and provides actions for users to address (or dismiss the banner). It requires a user action to be dismissed. + +Banners should be displayed at the top of the screen, below a top app bar. They’re persistent and nonmodal, allowing the user to either ignore them or interact with them at any time. Only one banner should be shown at a time + +![Banner light](images/banner-light.png) +![Banner dark](images/banner-dark.png) + ## Specifications references - [Design System Manager - Banners](https://system.design.orange.com/0c1af118d/p/85a52b-components/b/1497a4) @@ -30,37 +39,71 @@ Please follow [accessibility criteria for development](https://a11y-guidelines.o ### No button ```swift -ODSBanner(text: "Two line text string with two actions. One to two lines is preferable on mobile and tablet.", - image: Image("ods_empty", bundle: Bundle.ods)) +ODSBanner(text: "One to two lines is preferable on mobile and tablet.", + imageSource: .image(Image("ods_empty", bundle: Bundle.ods))) ``` ### One button -* Placed next to the text +The button is placed under the text. ```swift -ODSBanner(text: "Two line text string with two actions. One to two lines is preferable on mobile and tablet.", - image: Image("ods_empty", bundle: Bundle.ods), - button: ODSButton(text: "Button", emphasis: .low) {}, - position: .trailing) +ODSBanner(text: "One to two lines is preferable on mobile and tablet.", + imageSource: .image(Image("ods_empty", bundle: Bundle.ods))) { + Button("Button") { + // your action here + } +} ``` -* Placed under the text +### Two buttons + +Button are placed under the text. ```swift -ODSBanner(text: "Two line text string with two actions. One to two lines is preferable on mobile and tablet.", - image: Image("ods_empty", bundle: Bundle.ods), - button: ODSButton(text: "Button", emphasis: .low) {}, - position: .bottom) +ODSBanner(text: "One to two lines is preferable on mobile and tablet.", + imageSource: .image(Image("ods_empty", bundle: Bundle.ods))) { + Button("Button 1") { + // your action here + } +} secondButton: { + Button("Button 1") { + // your action here + } +} ``` -### Two buttons +### Without image ```swift -ODSBanner(text: "Two line text string with two actions. One to two lines is preferable on mobile and tablet.", - image: Image("ods_empty", bundle: Bundle.ods), - leadingButton: ODSButton(text: "Button 1", emphasis: .low) {}, - trailingButton: ODSButton(text: "Button 2", emphasis: .low) {}) +ODSBanner(text: "One to two lines is preferable on mobile and tablet.") { + Button("Button 1") { + // your action here + } +} secondButton: { + Button("Button 1") { + // your action here + } +} +``` + +### With image from url + +```swift + +let placeholder = Image("ods_empty", bundle: Bundle.ods) +let url = URL(string: "https://images.unsplash.com/photo-1615735487485-e52b9af610c1?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=774&q=80") + +ODSBanner(text: "One to two lines is preferable on mobile and tablet.", + imageSource: .asyncImage(url, placeholder)) { + Button("Button 1") { + // your action here + } +} secondButton: { + Button("Button 1") { + // your action here + } +} ``` diff --git a/docs/components/buttons.md b/docs/components/buttons.md index 8489f6e4..8779ba56 100644 --- a/docs/components/buttons.md +++ b/docs/components/buttons.md @@ -33,17 +33,15 @@ Please follow [accessibility criteria for development](https://a11y-guidelines.o Button variants range in style to denote emphasis. Use different styles and not size to show the preferred choice. ```swift -// Highest emphasis +// High emphasis ODSButton(text: "Some text", image: Image("Add"), - emphasis: .highest, - variableWidth: true) {} + emphasis: .high) {} -// Low emphasis +// Lowest emphasis ODSButton(text: "Some text", image: Image("Add"), - emphasis: .low, - variableWidth: true) {} + emphasis: .lowest) {} ``` ### Functional button diff --git a/docs/components/cards.md b/docs/components/cards.md index eb9c18a8..d805e028 100644 --- a/docs/components/cards.md +++ b/docs/components/cards.md @@ -42,7 +42,7 @@ This is a full width card displayed with an image as first element. This card is composed of two parts: - Media: (today an image) -- Content: with a title, an optinal subtitle an optinal supporting text and optional buttons (zero up to two) +- Content: with a title, an optional subtitle an optional supporting text and optional buttons (zero up to two) > **Implementation** @@ -56,11 +56,11 @@ let model = ODSCardVerticalImageFirstModel( supportingText: "A supporting text to describe something") ODSCardVerticalImageFirst(model: model) { - ODSButton(text: "Button 1", emphasis: .medium) { + ODSButton(text: "Button 1", emphasis: .low) { // do something here } } buttonContent2: { - ODSButton(text: "Button 1", emphasis: .medium) { + ODSButton(text: "Button 1", emphasis: .low) { // do something here } } @@ -72,30 +72,29 @@ ODSCardVerticalImageFirst(model: model) { This is a full width card displaying with a title and a thumbnail on top as first element. This card is composed of three parts: -- Header: with a title, an optinal subtitle and an optinal thmubnail +- Header: with a title, an optional subtitle and an optional thmubnail - Media: (today an image) -- Content: with an optinal supporting text and optional buttons (zero up to two) +- Content: with an optional supporting text and optional buttons (zero up to two) > **Implementation** Card is configured using `ODSCardVerticalHeaderFirstModel` like this: ```swift -let model = ODSCardVerticalHeaderFirstModel( - title: "Title", - subtitle: "Subtitle", - thumbnail: Image("ods_empty", bundle: Bundle.ods), - image: Image("ods_empty", bundle: Bundle.ods), - supportingText: "A supporting text to describe something") -ODSCardVerticalHeaderFirst(model: model) { - ODSButton(text: "Button 1", emphasis: .medium) { - // do something here - } - } buttonContent2: { - ODSButton(text: "Button 1", emphasis: .medium) { - // do something here - } +ODSCardVerticalHeaderFirst( + title: Text("Title"), + imageSource: .image(Image("ods_empty", bundle: Bundle.ods)), + subtitle: Text("Subtitle"), + thumbnail: Image("ods_empty", bundle: Bundle.ods), + text: Text("A supporting text to describe something") +) { + Button("Button 1") { + // do something here + } +} secondButton: { + Button("Button 2") { + // do something here } } ``` @@ -107,28 +106,27 @@ This is a full width card displaying with image on left and content with texts o Thes content is composed by: - a title - an optional subtitle -- an optional supporting text for larger description +- an optional text for larger description > **Implementation** -Card is configured using `ODSCardHorizontalModel` like this: +Card is configured like this: -```swift -let model = ODSCardHorizontalModel( - title: "Title", - subtitle: "Subtitle", +```swift +ODSCardHorizontal( + title: Text("Title"), imageSource: .image(Image("ods_empty", bundle: Bundle.ods)), imagePosition: .leading, - supportingText: "A supporting text to describe something") - -ODSCardHorizontal(model: model) { - ODSButton(text: "Button 1", emphasis: .medium) { - // do something here - } - } buttonContent2: { - ODSButton(text: "Button 1", emphasis: .medium) { + subtitle: Text("Subtitle"), + text: Text("A supporting text to describe something") +) { + + Button("Button 1") { // do something here - } + } +} secondButton : { + Button("Button 1") { + // do something here } } ``` @@ -136,7 +134,7 @@ ODSCardHorizontal(model: model) { ### Small Card The small card if prefered for two-column portrait mobile screen display. -As it is smaller than full-width cards, it contains only title and subtitle (optinal) in one line (Truncated tail). +As it is smaller than full-width cards, it contains only title and subtitle (optional) in one line (Truncated tail). > **Implementation** diff --git a/docs/components/chips.md b/docs/components/chips.md index b3c27ca8..4c8dd841 100644 --- a/docs/components/chips.md +++ b/docs/components/chips.md @@ -71,7 +71,7 @@ The selection is managed by the `ODSChipPicker` providing the right type of sele ### Single selection -The option allows a single chip selection from a set of options. According to the type of selection (optinal or not), it is possible to accept at least one or zero selected chip. +The option allows a single chip selection from a set of options. According to the type of selection (optional or not), it is possible to accept at least one or zero selected chip. #### Single selection, One chip selected diff --git a/docs/components/images/banner-dark.png b/docs/components/images/banner-dark.png new file mode 100644 index 00000000..769144eb Binary files /dev/null and b/docs/components/images/banner-dark.png differ diff --git a/docs/components/images/banner-light.png b/docs/components/images/banner-light.png new file mode 100644 index 00000000..ec84c11f Binary files /dev/null and b/docs/components/images/banner-light.png differ diff --git a/docs/modules/about.md b/docs/modules/about.md index a8edd6c5..50f541e8 100644 --- a/docs/modules/about.md +++ b/docs/modules/about.md @@ -153,11 +153,10 @@ ODSAboutModule(applicationInformation: withVersion, ...) - To activate the Share the application action ```swift -let storeUrl = URL(string: "http://oran.ge/dsapp")! ler shareTheApplicationConfiguration = ODSAboutShareTheApplication( - storeUrl: storeUrl, + storeUrl: URL(string: "http://oran.ge/dsapp")!, subject: "The Orange Design System", - description: "You will find the Orange Design System Mobile App that provides examples of design implementations at: \(storeUrl.absoluteString)" + description: "Here you will find the Orange Design System Mobile App that provides examples of design implementations" ) let withShareTheApp = ODSAboutApplicationInformation( diff --git a/docs/modules/about_docs.html b/docs/modules/about_docs.md similarity index 100% rename from docs/modules/about_docs.html rename to docs/modules/about_docs.md diff --git a/docs_release/README.md b/docs_release/README.md new file mode 100644 index 00000000..d236531b --- /dev/null +++ b/docs_release/README.md @@ -0,0 +1,124 @@ +# ODS iOS release guide + +This file lists all the steps to follow when releasing a new version of ODS iOS. + +- [Prepare release](#prepare-release) +- [Release](#release) + * [Publish release to GitHub](#publish-release-to-github) + * [Announce the new release on FoODS](#announce-the-new-release-on-foods)

+- [Prepare Next Release] + +## Prepare release + +- Create a branch named `prepare-release` to prepare the new release for ODS iOS version X.Y.Z. +- Switch to this branch and apply following chnages: + + - Update the changelog. + + ``` + \## [Unreleased]\(https://github.com/Orange-OpenSource/ods-ios/compare/P.Q.R...qualif) + ``` + to + + ``` + \## [X.Y.Z]\(https://github.com/Orange-OpenSource/ods-ios/compare/P.Q.R...X.Y.Z) - YYYY-MM-dd + ``` + where P.Q.R is the previous version, X.Y.Z the one we are releasing. + + - Update the AppNews file store in the resources of the demo application + `OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/AppNews.json` + + Add a new entry: + ``` + { + "version": "X.Y.Z", + "date": "YYYY-MM-dd", + "news": "Add news here" + }, + + ``` + + - Commit your modifications + - Push them to the repository + +- Create a new pull request named `Prepare release X.Y.Z` on GitHub to merge your branch into `qualif`. +- Review and merge this pull request on GitHub.

+ +## Release + +- Create a new pull request named `Release X.Y.Z` on GitHub to merge `qualif` into `main`. +- Review and merge this pull request on GitHub. +- Launch a job on your runner to build the demo application + - Using fastlane command: + ``` + # Variables for application signing + export ODS_DEVELOPER_APP_IDENTIFIER= + export ODS_FASTLANE_APPLE_ID= + export ODS_DEVELOPER_PORTAL_TEAM_ID= + + # Variables to Upload to TestFlight + export ODS_APPLE_KEY_ID = + export ODS_APPLE_ISSUER_ID = + export ODS_APPLE_KEY_CONTENT = + + fastlane prod upload: true" + # set true if you want to upload app to Test Flight, false otrherwise. + ``` + +### Publish release to GitHub + +- Go to [GitHub Releases](https://github.com/Orange-OpenSource/ods-ios/releases). + +- Click on `Draft a new version`. + + ![Edit GitHub release](images/github_release_01.png) + +- Create a new tag X.Y.Z + + ![Create tag](images/github_release_02.png) + +- Add release notes and verify using the preview tab. + +- Optionally check `Set as a pre-release` and click `Publish release`.

+ +### Announce the new release on FoODS + +- Go to [Teams - FoODS: ODS Mobile iOS] + +- Post a message with screenshots of new elements to announce the release. + + As Microsoft Teams does not support pasting Markdown, open the text below in a Markdown editor that produces rich text, copy the rich text and paste it to Microsoft Teams. Finally edit the message to display the emojis and add newlines wherever needed. + + + > (rocket) Release X.Y.Z is available! + > + > **New components** + >- C8 + >- C9 + > + >**New module** + >- M1 + > + >(page) [Release note](https://github.com/Orange-OpenSource/ods-ios/releases/tag/X.Y.Z) + > + >(phone) [Demo app]("http://oran.ge/dsapp") + > + +## [Prepare Next Release] + +- Create a branch named `prepare-new-release` to prepare the new release for ODS iOS version U.V.W. + +- Switch to this branch and apply following chnages: + + - Update the changelog. + + Add a section like: + ``` + \## [Unreleased]\(https://github.com/Orange-OpenSource/ods-ios/compare/X.Y.Z...qualif) + ``` + + - Update in Xcode the version of OrangeDesigneSystemDemo targetto U.V.W (the new version) + - Commit your modifications + - Push them to the repository + - Create a new pull request named `Update release U.V.W` on GitHub to merge your branch into `qualif`. + - Review and merge this pull request on GitHub.

diff --git a/docs_release/images/github_release_01.png b/docs_release/images/github_release_01.png new file mode 100644 index 00000000..eb4f2b78 Binary files /dev/null and b/docs_release/images/github_release_01.png differ diff --git a/docs_release/images/github_release_02.png b/docs_release/images/github_release_02.png new file mode 100644 index 00000000..2e8a685b Binary files /dev/null and b/docs_release/images/github_release_02.png differ