diff --git a/stencils/Color+Color.stencil b/stencils/Color+Color.stencil new file mode 100644 index 0000000..4217899 --- /dev/null +++ b/stencils/Color+Color.stencil @@ -0,0 +1,14 @@ +{% if argument.includeResources %} +import SwiftUI +import {{ argument.sharedFrameworkName }} + +extension {{ argument.sharedFrameworkName }}.Color { + var swiftUI: SwiftUI.Color { + if #available(iOS 15.0, *) { + return SwiftUI.Color(uiColor: self.uiColor) + } else { + return SwiftUI.Color(red: self.red, green: self.green, blue: self.blue, opacity: self.alpha) + } + } +} +{% endif %} \ No newline at end of file diff --git a/stencils/DefaultValues.stencil b/stencils/DefaultValues.stencil index bc07b0b..57c5100 100644 --- a/stencils/DefaultValues.stencil +++ b/stencils/DefaultValues.stencil @@ -1,4 +1,5 @@ import UIKit +import SwiftUI import Foundation extension Array: HasDefaultValue { @@ -29,6 +30,25 @@ extension UIColor: HasDefaultValue { static func `default`() -> Self { UIColor.clear as! Self } } +extension SwiftUI.Color: HasDefaultValue { + static func `default`() -> Self { SwiftUI.Color.clear } +} + +extension UIImage: HasDefaultValue { + static func `default`() -> Self { + UIGraphicsBeginImageContext(CGSize(width: 1, height: 1)) + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return image as! Self + } +} + +extension Image: HasDefaultValue { + static func `default`() -> Self { + return Image(uiImage: UIImage.default()) + } +} + extension Date: HasDefaultValue { static func `default`() -> Date { Date() } } diff --git a/stencils/KalugaBackgroundStyle+SwiftUI.stencil b/stencils/KalugaBackgroundStyle+SwiftUI.stencil index 4a6bcdf..9a5ad44 100644 --- a/stencils/KalugaBackgroundStyle+SwiftUI.stencil +++ b/stencils/KalugaBackgroundStyle+SwiftUI.stencil @@ -83,6 +83,15 @@ extension {{ argument.sharedFrameworkName }}.BackgroundStyle { } } +extension View { + public func background(_ backgroundStyle: {{ argument.sharedFrameworkName }}.BackgroundStyle) -> some View { + ModifiedContent( + content: self, + modifier: BackgroundStyleModifier(backgroundStyle: backgroundStyle) + ) + } +} + // MARK: - Private methods private extension {{ argument.sharedFrameworkName }}.BackgroundStyle { @@ -222,6 +231,15 @@ private extension {{ argument.sharedFrameworkName }}.BackgroundStyle { */ } +fileprivate struct BackgroundStyleModifier: ViewModifier { + let backgroundStyle: {{ argument.sharedFrameworkName }}.BackgroundStyle + + func body(content: Content) -> some View { + content + .background(backgroundStyle.toView()) + } +} + // MARK: - RoundedShape fileprivate struct RoundedShape: Shape { diff --git a/stencils/KalugaButton+SwiftUI.stencil b/stencils/KalugaButton+SwiftUI.stencil index ee8f6a3..a9b3f58 100644 --- a/stencils/KalugaButton+SwiftUI.stencil +++ b/stencils/KalugaButton+SwiftUI.stencil @@ -23,8 +23,16 @@ extension KalugaButton { }) { buttonView } - .buttonStyle(CustomButtonStyle(kalugaButton: self)) - .disabled(!self.isEnabled) + .buttonStyle(self.style, isEnabled: self.isEnabled) + } +} + +extension View { + public func buttonStyle(_ buttonStyle: {{ argument.sharedFrameworkName }}.ButtonStyle, isEnabled: Bool = true) -> some View { + ModifiedContent( + content: self, + modifier: ButtonStyleModifier(buttonStyle: buttonStyle, isEnabled: isEnabled) + ) } } @@ -32,84 +40,56 @@ extension KalugaButton { private extension KalugaButton { + private var textStyle: TextStyle { + return TextStyle(font: style.font, color: style.defaultStyle.textColor, size: style.textSize, alignment: style.textAlignment) + } + private func makePlainButton(button: KalugaButton.Plain) -> AnyView { - // create the font - let font = Font( - style.font - .withSize(CGFloat(style.textSize)) - ) - - // create the text alignment - let textAlignment: Alignment = { - switch style.textAlignment { - case TextAlignment.left: - return .leading - case TextAlignment.right: - return .trailing - case TextAlignment.end: - return UIApplication.shared.userInterfaceLayoutDirection == .leftToRight ? - .trailing : - .leading - case TextAlignment.start: - return .leading - case TextAlignment.center: - return .center - default: - preconditionFailure("Unknown text alignment type!") - } - }() - // assemble the button return AnyView( Text(button.text) - .font(font) - .frame(alignment: textAlignment) + .textStyle(textStyle) ) } - private func makeStyledButton(button: KalugaButton.Styled) -> AnyView { return AnyView( - button.text.toText() + KalugaLabel.Styled(text: button.text, style: textStyle).toAttributedText() ) } } -// MARK: - KalugaButton.CustomButtonStyle +// MARK: - CustomButtonStyle -private extension KalugaButton { +fileprivate struct CustomButtonStyle: SwiftUI.ButtonStyle { + private let buttonStyle: {{ argument.sharedFrameworkName }}.ButtonStyle + private let isEnabled: Bool + init(buttonStyle: {{ argument.sharedFrameworkName }}.ButtonStyle, isEnabled: Bool = true) { + self.buttonStyle = buttonStyle + self.isEnabled = isEnabled + } + func makeBody(configuration: Self.Configuration) -> some View { + let stateStyle = stateStyle(isPressed: configuration.isPressed) + return configuration.label + .padding() + .foregroundColor(stateStyle.textColor.swiftUI) + .background(stateStyle.backgroundStyle) + } + + private func stateStyle(isPressed: Bool) -> ButtonStateStyle { + if !isEnabled { return buttonStyle.disabledStyle } + else if isPressed { return buttonStyle.pressedStyle } + else { return buttonStyle.defaultStyle } + } +} + +fileprivate struct ButtonStyleModifier: ViewModifier { + let buttonStyle: {{ argument.sharedFrameworkName }}.ButtonStyle + let isEnabled: Bool - struct CustomButtonStyle: SwiftUI.ButtonStyle { - - private let kalugaButton: KalugaButton - - init(kalugaButton: KalugaButton) { - self.kalugaButton = kalugaButton - } - - func makeBody(configuration: Self.Configuration) -> some View { - let foregroundColor = makeForegroundColor(isPressed: configuration.isPressed) - let shape = makeShape(isPressed: configuration.isPressed) - - return configuration.label - .padding() - .foregroundColor(foregroundColor) - .background(shape) - } - - private func makeForegroundColor(isPressed: Bool) -> SwiftUI.Color { - if !kalugaButton.isEnabled { return Color(kalugaButton.style.disabledStyle.textColor.uiColor) } - else if isPressed { return Color(kalugaButton.style.pressedStyle.textColor.uiColor) } - else { return Color(kalugaButton.style.defaultStyle.textColor.uiColor) } - } - - private func makeShape(isPressed: Bool) -> some View { - let buttonStateStyle: ButtonStateStyle - if !kalugaButton.isEnabled { buttonStateStyle = kalugaButton.style.disabledStyle } - else if isPressed { buttonStateStyle = kalugaButton.style.pressedStyle } - else { buttonStateStyle = kalugaButton.style.defaultStyle} - - return buttonStateStyle.backgroundStyle.toView() - } + func body(content: Content) -> some View { + content + .buttonStyle(CustomButtonStyle(buttonStyle: buttonStyle, isEnabled: isEnabled)) + .disabled(!isEnabled) } } {% endif %} diff --git a/stencils/KalugaLabel+SwiftUI.stencil b/stencils/KalugaLabel+SwiftUI.stencil index e16724c..d5b7927 100644 --- a/stencils/KalugaLabel+SwiftUI.stencil +++ b/stencils/KalugaLabel+SwiftUI.stencil @@ -18,50 +18,63 @@ extension KalugaLabel { } } +extension View { + public func textStyle(_ textStyle: TextStyle) -> some View { + ModifiedContent( + content: self, + modifier: TextStyleModifier(textStyle: textStyle) + ) + } +} + +extension {{ argument.sharedFrameworkName }}.TextAlignment { + + var textAlignment: SwiftUI.TextAlignment { + switch self { + case TextAlignment.left: + return .leading + case TextAlignment.right: + return .trailing + case TextAlignment.end: + return UIApplication.shared.userInterfaceLayoutDirection == .leftToRight ? + .trailing : + .leading + case TextAlignment.start: + return .leading + case TextAlignment.center: + return .center + default: + preconditionFailure("Unknown text alignment type!") + } + } +} + // MARK: - Private methods private extension KalugaLabel { private func makePlainLabel(label: KalugaLabel.Plain) -> AnyView { - // create the font - let font = Font( - style.font - .withSize(CGFloat(style.size)) - ) - - // create the text alignment - let textAlignment: Alignment = { - switch style.alignment { - case TextAlignment.left: - return .leading - case TextAlignment.right: - return .trailing - case TextAlignment.end: - return UIApplication.shared.userInterfaceLayoutDirection == .leftToRight ? - .trailing : - .leading - case TextAlignment.start: - return .leading - case TextAlignment.center: - return .center - default: - preconditionFailure("Unknown text alignment type!") - } - }() - // assemble the button return AnyView( Text(label.text) - .font(font) - .foregroundColor(Color(style.color.uiColor)) - .frame(alignment: textAlignment) + .textStyle(style) ) } - private func makeStyledLabel(label: KalugaLabel.Styled) -> AnyView { return AnyView( - label.text.toText() + label.toAttributedText() ) } } + +fileprivate struct TextStyleModifier: ViewModifier { + let textStyle: TextStyle + + func body(content: Content) -> some View { + content + .font(Font(textStyle.font.withSize(CGFloat(textStyle.size)))) + .foregroundColor(Color(textStyle.color.uiColor)) + .multilineTextAlignment(textStyle.alignment.textAlignment) + } +} {% endif %} diff --git a/stencils/KalugaStyledString+SwiftUI.stencil b/stencils/KalugaStyledString+SwiftUI.stencil index 18f25ba..00de84f 100644 --- a/stencils/KalugaStyledString+SwiftUI.stencil +++ b/stencils/KalugaStyledString+SwiftUI.stencil @@ -4,23 +4,19 @@ import {{ argument.sharedFrameworkName }} // MARK: - StyledString -extension {{ argument.sharedFrameworkName }}.StyledString { - - func toText() -> some View { - AttributedText(attributeString) +extension KalugaLabel.Styled { + func toAttributedText() -> some View { + AttributedText(self) } } // MARK: - AttributedText fileprivate struct AttributedText: UIViewRepresentable { - - private let attributedString: NSAttributedString - - init(_ attributedString: NSAttributedString) { - self.attributedString = attributedString + private let styledLabel: KalugaLabel.Styled + init(_ styledLabel: KalugaLabel.Styled) { + self.styledLabel = styledLabel } - func makeUIView(context: Context) -> UILabel { let label = UILabel() label.lineBreakMode = .byClipping @@ -29,9 +25,8 @@ fileprivate struct AttributedText: UIViewRepresentable { label.setContentCompressionResistancePriority(.defaultLow, for: .vertical) return label } - func updateUIView(_ uiView: UILabel, context: Context) { - uiView.attributedText = attributedString + TextStyleKt.bindLabel(uiView, label: styledLabel) } } {% endif %} diff --git a/stencils/ListObservable.stencil b/stencils/ListObservable.stencil index 137bd8c..f9bc3e1 100644 --- a/stencils/ListObservable.stencil +++ b/stencils/ListObservable.stencil @@ -32,7 +32,14 @@ class ListObservable: ObservableObject { defer { self.input = newValue } - guard newValue != nil, self.input != newValue else { + guard newValue != nil else { + if self.input != nil { + self.value.removeAll() + self.value.append(contentsOf: defaultValue) + } + return + } + guard self.input != newValue else { return } let mapped = mapper(newValue!) diff --git a/stencils/ListSubject.stencil b/stencils/ListSubject.stencil index 8de4b74..5d48965 100644 --- a/stencils/ListSubject.stencil +++ b/stencils/ListSubject.stencil @@ -39,7 +39,14 @@ class ListSubject: ObservableObject { defer { self.input = newValue } - guard newValue != nil, self.input != newValue else { + guard newValue != nil else { + if self.input != nil { + self.value.removeAll() + self.value.append(contentsOf: defaultValue) + } + return + } + guard self.input != newValue else { return } let mapped = mapper(newValue!) diff --git a/stencils/NavigationBarColor.stencil b/stencils/NavigationBarColor.stencil index 3eb8651..eaf6b0f 100644 --- a/stencils/NavigationBarColor.stencil +++ b/stencils/NavigationBarColor.stencil @@ -1,25 +1,27 @@ +{% if argument.includeResources %} import SwiftUI +import {{ argument.sharedFrameworkName }} struct NavigationBarColor: ViewModifier { - init(backgroundColor: Color, tintColor: Color, shadowColor: Color, font: UIFont, largeFont: UIFont) { + init(backgroundColor: {{ argument.sharedFrameworkName }}.Color, tintColor: {{ argument.sharedFrameworkName }}.Color, shadowColor: {{ argument.sharedFrameworkName }}.Color, font: UIFont, largeFont: UIFont) { let coloredAppearance = UINavigationBarAppearance() coloredAppearance.configureWithOpaqueBackground() - coloredAppearance.backgroundColor = UIColor(backgroundColor) - coloredAppearance.shadowColor = UIColor(shadowColor) + coloredAppearance.backgroundColor = backgroundColor.uiColor + coloredAppearance.shadowColor = shadowColor.uiColor coloredAppearance.titleTextAttributes = [ - .foregroundColor: UIColor(tintColor), + .foregroundColor: tintColor.uiColor, .font: font ] coloredAppearance.largeTitleTextAttributes = [ - .foregroundColor: UIColor(tintColor), + .foregroundColor: tintColor.uiColor, .font: largeFont ] UINavigationBar.appearance().standardAppearance = coloredAppearance UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance UINavigationBar.appearance().compactAppearance = coloredAppearance - UINavigationBar.appearance().tintColor = UIColor(tintColor) + UINavigationBar.appearance().tintColor = tintColor.uiColor } func body(content: Content) -> some View { @@ -29,9 +31,9 @@ struct NavigationBarColor: ViewModifier { extension View { func navigationBarColor( - backgroundColor: Color, - tintColor: Color, - shadowColor: Color, + backgroundColor: {{ argument.sharedFrameworkName }}.Color, + tintColor: {{ argument.sharedFrameworkName }}.Color, + shadowColor: {{ argument.sharedFrameworkName }}.Color, font: UIFont = .systemFont(ofSize: 17, weight: .semibold), largeFont: UIFont = .boldSystemFont(ofSize: 34) ) -> some View { @@ -46,3 +48,4 @@ extension View { ) } } +{% endif %} \ No newline at end of file diff --git a/stencils/Observable.stencil b/stencils/Observable.stencil index 29f1200..182b5e8 100644 --- a/stencils/Observable.stencil +++ b/stencils/Observable.stencil @@ -11,7 +11,10 @@ typealias IntObservable = MappedWithDefaultObservable typealias FloatObservable = MappedWithDefaultObservable typealias DoubleObservable = MappedWithDefaultObservable {% if argument.includeResources %} -typealias ColorObservable = MappedWithDefaultObservable<{{ argument.sharedFrameworkName }}.Color, UIColor, ColorMapper> +typealias UIColorObservable = MappedWithDefaultObservable<{{ argument.sharedFrameworkName }}.Color, UIColor, UIColorMapper> +typealias ColorObservable = MappedWithDefaultObservable<{{ argument.sharedFrameworkName }}.Color, SwiftUI.Color, ColorMapper> +typealias UIImageObservable = MappedWithDefaultObservable> +typealias ImageObservable = MappedWithDefaultObservable {% endif %} typealias DateObservable = MappedWithDefaultObservable typealias ObjectSimpleObservable = MappedObservable> @@ -44,7 +47,13 @@ class Observable: ObservableObject { defer { self.input = newValue } - guard newValue != nil, self.input != newValue else { + guard newValue != nil else { + if (self.input != nil) { + self.value = defaultValue + } + return + } + guard self.input != newValue else { return } let mapped = mapper(newValue!) @@ -70,7 +79,7 @@ class Observable: ObservableObject { class MappedObservable< Input: KotlinObject, Output: Equatable, - Mapper: PlatformValueMapper + Mapper: PlatformValueToMapper >: Observable where Mapper.Input == Input, Mapper.Output == Output { init(_ observable: BaseInitializedObservable, defaultValue: Output, animated: Bool = false) { super.init(observable, defaultValue: defaultValue, animated: animated, mapper: Mapper.to) @@ -80,7 +89,7 @@ class MappedObservable< class MappedWithDefaultObservable< Input: KotlinObject, Output: Equatable & HasDefaultValue, - Mapper: PlatformValueMapper + Mapper: PlatformValueToMapper >: MappedObservable where Mapper.Input == Input, Mapper.Output == Output { override init( _ observable: BaseInitializedObservable, diff --git a/stencils/PlatformMappers.stencil b/stencils/PlatformMappers.stencil index a565bff..409622d 100644 --- a/stencils/PlatformMappers.stencil +++ b/stencils/PlatformMappers.stencil @@ -1,4 +1,5 @@ import UIKit +import SwiftUI import Foundation import {{ argument.sharedFrameworkName }} @@ -99,12 +100,12 @@ enum DateOptionalMapper: PlatformOptionalValueMapper { } {% if argument.includeResources %} -enum ColorMapper: PlatformValueMapper { +enum UIColorMapper: PlatformValueMapper { static func to(_ value: {{ argument.sharedFrameworkName }}.Color) -> UIColor { value.uiColor } static func from(_ value: UIColor) -> {{ argument.sharedFrameworkName }}.Color { {{ argument.sharedFrameworkName }}.Color(uiColor: value) } } -enum ColorOptionalMapper: PlatformOptionalValueMapper { +enum UIColorOptionalMapper: PlatformOptionalValueMapper { static func to(_ value: {{ argument.sharedFrameworkName }}.Color?) -> UIColor? { value?.uiColor } static func from(_ value: UIColor?) -> {{ argument.sharedFrameworkName }}.Color? { if let value = value { @@ -115,6 +116,28 @@ enum ColorOptionalMapper: PlatformOptionalValueMapper { } } +enum ColorMapper: PlatformValueToMapper { + static func to(_ value: {{ argument.sharedFrameworkName }}.Color) -> SwiftUI.Color { value.swiftUI } +} + +enum ColorOptionalMapper: PlatformOptionalValueToMapper { + static func to(_ value: {{ argument.sharedFrameworkName }}.Color?) -> SwiftUI.Color? { value?.swiftUI } +} + +enum ImageMapper: PlatformValueToMapper { + static func to(_ value: UIImage) -> Image { Image(uiImage: value) } +} + +enum ImageOptionalMapper: PlatformOptionalValueToMapper { + static func to(_ value: UIImage?) -> Image? { + if let uiImage = value { + return Image(uiImage: uiImage) + } else { + return nil + } + } +} + {% endif %} enum EmptyMapper: PlatformValueMapper { static func to(_ value: T) -> T { value } diff --git a/stencils/PlatformValueMapper.stencil b/stencils/PlatformValueMapper.stencil index 4c67f95..0b1f1fd 100644 --- a/stencils/PlatformValueMapper.stencil +++ b/stencils/PlatformValueMapper.stencil @@ -1,15 +1,35 @@ -protocol PlatformValueMapper { +protocol PlatformValueToMapper { associatedtype Input associatedtype Output static func to(_ value: Input) -> Output +} + +protocol PlatformValueFromMapper { + associatedtype Input + associatedtype Output + static func from(_ value: Output) -> Input } -protocol PlatformOptionalValueMapper { +protocol PlatformValueMapper : PlatformValueToMapper, PlatformValueFromMapper { + +} + +protocol PlatformOptionalValueToMapper { associatedtype Input associatedtype Output static func to(_ value: Input?) -> Output? +} + +protocol PlatformOptionalValueFromMapper { + associatedtype Input + associatedtype Output + static func from(_ value: Output?) -> Input? } + +protocol PlatformOptionalValueMapper : PlatformOptionalValueToMapper, PlatformOptionalValueFromMapper { + +} diff --git a/stencils/Subject.stencil b/stencils/Subject.stencil index 609cabd..e2fa8c5 100644 --- a/stencils/Subject.stencil +++ b/stencils/Subject.stencil @@ -46,7 +46,13 @@ class Subject: ObservableObject { defer { self.input = newValue } - guard newValue != nil, self.input != newValue else { + guard newValue != nil else { + if (self.input != nil) { + self.value = defaultValue + } + return + } + guard self.input != newValue else { return } let mapped = toMapper(newValue!) diff --git a/stencils/UninitializedObservable.stencil b/stencils/UninitializedObservable.stencil index 9c16852..630192f 100644 --- a/stencils/UninitializedObservable.stencil +++ b/stencils/UninitializedObservable.stencil @@ -9,7 +9,10 @@ typealias IntUninitializedObservable = MappedUninitializedObservable typealias DoubleUninitializedObservable = MappedUninitializedObservable {% if argument.includeResources %} -typealias ColorUninitializedObservable = MappedUninitializedObservable<{{ argument.sharedFrameworkName }}.Color, UIColor, ColorOptionalMapper> +typealias UIColorUninitializedObservable = MappedUninitializedObservable<{{ argument.sharedFrameworkName }}.Color, UIColor, UIColorOptionalMapper> +typealias ColorUninitializedObservable = MappedUninitializedObservable<{{ argument.sharedFrameworkName }}.Color, SwiftUI.Color, ColorOptionalMapper> +typealias UIImageUninitializedObservable = MappedUninitializedObservable> +typealias ImageUninitializedObservable = MappedUninitializedObservable {% endif %} typealias DateUninitializedObservable = MappedUninitializedObservable @@ -64,7 +67,7 @@ class UninitializedObservable: Observabl class MappedUninitializedObservable< Input: KotlinObject, Output: Equatable, - Mapper: PlatformOptionalValueMapper + Mapper: PlatformOptionalValueToMapper >: UninitializedObservable where Mapper.Input == Input, Mapper.Output == Output { init(_ observable: BaseUninitializedObservable, animated: Bool = false) { super.init(observable, animated: animated, mapper: Mapper.to)