Digital ACT-191 ONEAPP-7013 story: added pagination model

This commit is contained in:
vasavk 2024-06-09 11:02:18 +05:30
parent 57a0486b3f
commit bc35186bf6
3 changed files with 160 additions and 105 deletions

View File

@ -26,6 +26,7 @@
18AE87542C06FE610075F181 /* CarouselChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */; };
18B42AC62C09D197008D6262 /* CarouselSlotAlignmentModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */; };
18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */; };
18B9763F2C11BA4A009271DF /* CarouselPaginationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B9763E2C11BA4A009271DF /* CarouselPaginationModel.swift */; };
18BDEE822B75316E00452358 /* ButtonIconChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */; };
18FEA1AD2BDD137500A56439 /* CalendarIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */; };
18FEA1B52BE0E63600A56439 /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */; };
@ -244,6 +245,7 @@
18AE87532C06FE610075F181 /* CarouselChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselChangeLog.txt; sourceTree = "<group>"; };
18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselSlotAlignmentModel.swift; sourceTree = "<group>"; };
18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownOptionModel.swift; sourceTree = "<group>"; };
18B9763E2C11BA4A009271DF /* CarouselPaginationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselPaginationModel.swift; sourceTree = "<group>"; };
18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ButtonIconChangeLog.txt; sourceTree = "<group>"; };
18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarIndicatorModel.swift; sourceTree = "<group>"; };
18FEA1B42BE0E63600A56439 /* Date+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = "<group>"; };
@ -503,6 +505,7 @@
isa = PBXGroup;
children = (
18AE874F2C06FDA60075F181 /* Carousel.swift */,
18B9763E2C11BA4A009271DF /* CarouselPaginationModel.swift */,
18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */,
18AE87532C06FE610075F181 /* CarouselChangeLog.txt */,
);
@ -1293,6 +1296,7 @@
EA5F86D02A1F936100BC83E4 /* TabsContainer.swift in Sources */,
EAF7F0B1289B177F00B287F5 /* ColorLabelAttribute.swift in Sources */,
EAC9258F2911C9DE00091998 /* EntryFieldBase.swift in Sources */,
18B9763F2C11BA4A009271DF /* CarouselPaginationModel.swift in Sources */,
EAB1D2EA28AE84AA00DAE764 /* UIControlPublisher.swift in Sources */,
EAD068922A560B65002E3A2D /* LoaderViewController.swift in Sources */,
71FC86DA2B96F44C00700965 /* PaginationButton.swift in Sources */,

View File

@ -37,30 +37,22 @@ open class Carousel: View {
open var aspectRatio: AspectRatio = .ratio1x1 { didSet { setNeedsUpdate() } }
/// Data used to render tilelets in the carousel.
open var data: Array? = [] { didSet { setNeedsUpdate() } }
open var data: [Any] = [] { didSet { setNeedsUpdate() } }
/// Space between each tile. The default value will be 24px in tablet and 12px in mobile.
open var gutter: Gutter? {
open var gutter: Gutter {
get { return _gutter }
set {
if let newValue {
_gutter = newValue
} else {
_gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX
}
_gutter = newValue
setNeedsUpdate()
}
}
/// The amount of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile.
open var layout: Layout? {
open var layout: Layout {
get { return _layout }
set {
if let newValue {
_layout = newValue
} else {
_layout = UIDevice.isIPad ? .threeUP : .oneUP
}
_layout = newValue
setNeedsUpdate()
}
}
@ -78,114 +70,49 @@ open class Carousel: View {
}
}
}
// TO DO: pagination
/// Config object for pagination.
/// Custom data type for pagination prop in this component.
open var pagination: CarouselPaginationModel {
get { return _pagination }
set {
_pagination = newValue
setNeedsUpdate()
}
}
/// If provided, will determine the conditions to render the pagination arrows.
open var paginationDisplay: PaginationDisplay? {
open var paginationDisplay: PaginationDisplay {
get { return _paginationDisplay }
set {
if let newValue {
_paginationDisplay = newValue
} else {
_paginationDisplay = .none
}
_paginationDisplay = newValue
setNeedsUpdate()
}
}
/// If provided, will apply margin to pagination arrows. Can be set to either positive or negative values.
/// The default value will be 12px in tablet and 8px in mobile.
open var paginationInset: CGFloat? {
open var paginationInset: CGFloat {
get { return _paginationInset }
set {
if let newValue {
_paginationInset = newValue
} else {
_paginationInset = UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space8X
}
_paginationInset = newValue
setNeedsUpdate()
}
}
/// Options for user to configure the partially-visible tile in group. Setting peek to 'none' will display arrow navigation icons on mobile devices.
open var peek: Peek? {
open var peek: Peek {
get { return _peek }
set {
if let newValue {
_peek = newValue
} else {
_peek = .none
}
_peek = newValue
setNeedsUpdate()
}
}
// TO DO: renderItem
/// Render item function. This will pass the data array and expects a react component in return.
// open var renderItem: ([] -> Any)? { // TO DO: return object and index
// get { nil }
// set {
// onScrollCancellable?.cancel()
// if let newValue {
// onScrollCancellable = onScrollPublisher
// .sink { c in
// newValue(c)
// }
// }
// }
// }
// open var renderItem: ([] -> Void)? {}
/// The initial visible slide's index in the carousel.
open var selectedIndex: Int? { didSet { setNeedsUpdate() } }
/// If provided, will set the alignment for slot content when the slots has different heights.
open var slotAlignment: [CarouselSlotAlignmentModel] = [] { didSet { setNeedsUpdate() } }
///
open var hidePaginationBorder: Bool = false { didSet { setNeedsUpdate() } }
/// viewport
/// viewportOverride
/// viewportOverrideObjectType
/// Custom data type for viewportOverride prop in this component.
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
// Sizes are from InVision design specs.
internal var containerSize: CGSize { CGSize(width: 320, height: 44) }
internal var containerView = View().with {
$0.clipsToBounds = true
}
/// Previous button to show previous slide.
private let previousButton: PaginationButton = .init(type: .previous)
/// Next button to show next slide.
private let nextButton: PaginationButton = .init(type: .next)
/// A publisher for when the scrubber position changes. Passes parameters (position).
open var onChangePublisher = PassthroughSubject<Int, Never>()
private var onChangeCancellable: AnyCancellable?
/// A publisher for when the carousel moves. Passes parameters (data).
// open var onScrollPublisher = PassthroughSubject<Array<Any>, Never>()
// private var onScrollCancellable: AnyCancellable?
internal var _layout: Layout = UIDevice.isIPad ? .threeUP : .oneUP
internal var _paginationDisplay: PaginationDisplay = .none
internal var _paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space8X
internal var _gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX
internal var _peek: Peek = .none
private var selectedGroupIndex: Int? { didSet { setNeedsUpdate() } }
// private var minPaginationInset: CGFloat {
// return UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space8X
// }
open var slotAlignment: [CarouselSlotAlignmentModel] = [] { didSet { setNeedsUpdate() } }
//--------------------------------------------------
// MARK: - Enums
@ -255,12 +182,7 @@ open class Carousel: View {
public enum Peek: String, CaseIterable {
case standard, minimum, none
}
/// Enum used to describe the pagination kind for pagination button icons.
public enum PaginationKind: String, CaseIterable {
case ghost, lowContrast, highContrast
} // TO DO: it should be used as pagination properties, validate desc stmt. and API - passing data is different.
// TO DO: move to model class
/// Enum used to describe the vertical of slotAlignment.
public enum Vertical: String, CaseIterable {
@ -271,6 +193,61 @@ open class Carousel: View {
public enum Horizontal: String, CaseIterable {
case left, center, right
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
// Sizes are from InVision design specs.
internal var containerSize: CGSize { CGSize(width: 320, height: 44) }
private let contentStackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.axis = .vertical
$0.distribution = .fill
$0.spacing = UIDevice.isIPad ? VDSLayout.space8X : VDSLayout.space6X
$0.backgroundColor = .clear
}
internal var carouselScrollBar = CarouselScrollbar().with {
$0.layout = UIDevice.isIPad ? .threeUP : .oneUP
$0.position = 0
$0.backgroundColor = .clear
}
internal var containerView = View().with {
$0.clipsToBounds = true
$0.backgroundColor = .clear
}
internal var scrollContainerView = View().with {
$0.clipsToBounds = true
$0.backgroundColor = .clear
}
private var scrollView = UIScrollView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.backgroundColor = .clear
}
/// A publisher for when the scrubber position changes. Passes parameters (position).
open var onChangePublisher = PassthroughSubject<Int, Never>()
private var onChangeCancellable: AnyCancellable?
internal var _layout: Layout = UIDevice.isIPad ? .threeUP : .oneUP
internal var _pagination: CarouselPaginationModel = .init(kind: .lowContrast, floating: true)
internal var _paginationDisplay: PaginationDisplay = .none
internal var _paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space8X
internal var _gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX
internal var _peek: Peek = .none
internal var _numberOfSlides: Int = 1
private var selectedGroupIndex: Int? { didSet { setNeedsUpdate() } }
private var containerStackHeightConstraint: NSLayoutConstraint?
private var containerViewHeightConstraint: NSLayoutConstraint?
// The scrollbar has top 5X space. So the expected top space is adjusted for tablet and mobile.
let space = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X
//--------------------------------------------------
// MARK: - Lifecycle
@ -282,22 +259,56 @@ open class Carousel: View {
open override func setup() {
super.setup()
isAccessibilityElement = false
// add containerView
addSubview(containerView)
containerView
.pinTop()
.pinBottom()
.pinLeadingGreaterThanOrEqualTo()
.pinTrailingLessThanOrEqualTo()
.height(containerSize.height)
// .width(containerSize.width)
.pinLeading()
.pinTrailing()
.heightGreaterThanEqualTo(containerSize.height)
containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate()
// add content stackview
containerView.addSubview(contentStackView)
// add scrollview
scrollContainerView.addSubview(scrollView)
// add pagination button icons
scrollContainerView.addSubview(pagination.previousButton)
pagination.previousButton
.pinLeading(paginationInset)
.pinCenterY()
scrollContainerView.addSubview(pagination.nextButton)
pagination.nextButton
.pinTrailing(paginationInset)
.pinCenterY()
// add scroll container view & carousel scrollbar
contentStackView.addArrangedSubview(scrollContainerView)
contentStackView.addArrangedSubview(carouselScrollBar)
contentStackView.setCustomSpacing(space, after: scrollContainerView)
contentStackView
.pinTop()
.pinBottom()
.pinLeading()
.pinTrailing()
.heightGreaterThanEqualTo(space+containerSize.height)
contentStackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate()
}
open override func updateView() {
containerViewHeightConstraint?.isActive = false
super.updateView()
containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: 200)
containerViewHeightConstraint?.isActive = true
pagination.previousButton.isHidden = (paginationDisplay == .none)
pagination.nextButton.isHidden = (paginationDisplay == .none)
layoutIfNeeded()
}
open override func reset() {

View File

@ -0,0 +1,40 @@
//
// CarouselPaginationModel.swift
// VDS
//
// Created by Kanamarlapudi, Vasavi on 06/06/24.
//
import Foundation
import UIKit
/// Custom data type for pagination prop for 'Carousel' component.
extension Carousel {
public struct CarouselPaginationModel {
/// Previous button to show previous slide.
public var previousButton = ButtonIcon().with {
$0.kind = .lowContrast
$0.iconName = .leftCaret
$0.iconOffset = .init(x: -2, y: 0)
$0.icon.size = UIDevice.isIPad ? .small : .xsmall
$0.size = UIDevice.isIPad ? .large : .small
}
/// Next button to show next slide.
public var nextButton = ButtonIcon().with {
$0.kind = .lowContrast
$0.iconName = .rightCaret
$0.iconOffset = .init(x: 2, y: 0)
$0.icon.size = UIDevice.isIPad ? .small : .xsmall
$0.size = UIDevice.isIPad ? .large : .small
}
public init(kind: ButtonIcon.Kind, floating: Bool) {
previousButton.kind = kind
nextButton.kind = kind
previousButton.floating = floating
nextButton.floating = floating
}
}
}