Skip to content

Commit

Permalink
Merge pull request #34 from oversizedev/develop
Browse files Browse the repository at this point in the history
Add DateField, PhoneField, PriceField amd URLField
  • Loading branch information
aromanov91 committed Jul 26, 2023
2 parents 0157917 + f290c35 commit f83c87e
Show file tree
Hide file tree
Showing 8 changed files with 376 additions and 1 deletion.
1 change: 0 additions & 1 deletion .github/workflows/bump.yml
Expand Up @@ -21,4 +21,3 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
WITH_V: false
DEFAULT_BUMP: patch
1 change: 1 addition & 0 deletions Sources/OversizeUI/Controls/Avatar/Avatar.swift
Expand Up @@ -78,6 +78,7 @@ public struct Avatar: View {
if let avatar {
avatar
.resizable()
.scaledToFill()
.frame(width: avatarSize, height: avatarSize)
.clipShape(Circle())
.overlay(Circle().stroke(strokeColor, lineWidth: 2))
Expand Down
93 changes: 93 additions & 0 deletions Sources/OversizeUI/Controls/DateField/DateField.swift
@@ -0,0 +1,93 @@
//
// Copyright © 2023 Alexander Romanov
// DateField.swift, created on 26.02.2023
//

import SwiftUI

#if os(iOS)
@available(iOS 15.0, *)
@available(macOS, unavailable)
@available(watchOS, unavailable)
@available(tvOS, unavailable)
public struct DateField: View {
@Environment(\.theme) private var theme: ThemeSettings
@Environment(\.fieldLabelPosition) private var fieldPlaceholderPosition: FieldLabelPosition
@Binding private var selection: Date
@Binding private var optionalSelection: Date?
private let label: String
@State private var showModal = false

let isOptionalSelection: Bool

public init(
_ sheetTitle: String = "Date",
selection: Binding<Date>
) {
label = sheetTitle
_selection = selection
_optionalSelection = .constant(nil)
isOptionalSelection = false
}

public init(
_ label: String = "Date",
selection: Binding<Date?>
) {
self.label = label
_selection = .constant(Date())
_optionalSelection = selection
isOptionalSelection = true
}

public var body: some View {
VStack(alignment: .leading, spacing: .xSmall) {
if fieldPlaceholderPosition == .adjacent {
HStack {
Text(label)
.subheadline(.medium)
.foregroundColor(.onSurfaceHighEmphasis)
Spacer()
}
}
Button {
showModal.toggle()
} label: {
VStack(alignment: .leading, spacing: .xxxSmall) {
if fieldPlaceholderPosition == .overInput {
Text(label)
.font(.subheadline)
.fontWeight(.semibold)
.onSurfaceDisabledForegroundColor()
}

HStack {
if isOptionalSelection, let optionalSelection {
Text(optionalSelection.formatted(date: .long, time: .shortened))
} else if isOptionalSelection {
Text(label)
} else {
Text(selection.formatted(date: .long, time: .shortened))
}
Spacer()
Icons.Base.calendar.outline
// Icon(.calendar, color: .onSurfaceHighEmphasis)
}
}
}
.buttonStyle(.field)
}
.sheet(isPresented: $showModal) {
if isOptionalSelection {
DatePickerSheet(title: label, selection: $optionalSelection)
.presentationDetents([.height(500)])
.presentationDragIndicator(.hidden)
} else {
DatePickerSheet(title: label, selection: $selection)
.presentationDetents([.height(500)])
.presentationDragIndicator(.hidden)
}
}
}
}
#endif
70 changes: 70 additions & 0 deletions Sources/OversizeUI/Controls/DateField/DatePickerSheet.swift
@@ -0,0 +1,70 @@
//
// Copyright © 2021 Alexander Romanov
// DatePickerSheet.swift, created on 11.12.2022
//

import SwiftUI

public struct DatePickerSheet: View {
@Environment(\.screenSize) var screenSize
@Environment(\.dismiss) var dismiss

@Binding private var selection: Date
@Binding private var optionalSelection: Date?
@State private var date: Date

private let title: String
private var minimumDate: Date?

public init(title: String, selection: Binding<Date>) {
self.title = title
_selection = selection
_date = State(wrappedValue: selection.wrappedValue)
_optionalSelection = .constant(nil)
}

public init(title: String, selection: Binding<Date?>) {
self.title = title
_date = State(wrappedValue: selection.wrappedValue ?? Date())
_optionalSelection = selection
_selection = .constant(Date())
}

public var body: some View {
PageView(title) {
SectionView {
VStack {
if let minimumDate {
DatePicker("", selection: $date, in: minimumDate...)
.datePickerStyle(.graphical)
.labelsHidden()
} else {
DatePicker("", selection: $date)
.datePickerStyle(.graphical)
.labelsHidden()
}
}
.padding(.horizontal, .small)
.padding(.vertical, .xxxSmall)
}
.surfaceContentInsets(.zero)
}
.backgroundSecondary()
.leadingBar {
BarButton(.close)
}
.trailingBar {
BarButton(.accent("Done", action: {
selection = date
optionalSelection = date
dismiss()
}))
}
}

public func datePickerMinimumDate(_ date: Date) -> DatePickerSheet {
var control = self
control.minimumDate = date
return control
}
}
31 changes: 31 additions & 0 deletions Sources/OversizeUI/Controls/PhoneField/PhoneField.swift
@@ -0,0 +1,31 @@
//
// Copyright © 2023 Alexander Romanov
// PhoneField.swift, created on 05.03.2023
//

import SwiftUI

#if os(iOS)
@available(iOS 15.0, *)
@available(macOS, unavailable)
@available(watchOS, unavailable)
@available(tvOS, unavailable)
public struct PhoneField: View {
@Binding private var phone: String

@State private var textFieldHelper: FieldHelperStyle = .none

public init(_ phone: Binding<String>) {
_phone = phone
}

public var body: some View {
TextField("+1 (000) 000 0000", text: $phone, onEditingChanged: { _ in
textFieldHelper = .none
}) {}
.keyboardType(.phonePad)
.textContentType(.nickname)
.fieldHelper(.constant("Invalid Phone"), style: $textFieldHelper)
}
}
#endif
37 changes: 37 additions & 0 deletions Sources/OversizeUI/Controls/PriceField/PriceField.swift
@@ -0,0 +1,37 @@
//
// Copyright © 2023 Alexander Romanov
// PriceField.swift, created on 15.03.2023
//

import Foundation
import SwiftUI

@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
public struct PriceField: View {
@Binding private var amount: Decimal
private let currency: Locale.Currency

public init(amount: Binding<Decimal>, currency: Locale.Currency) {
_amount = amount
self.currency = currency
}

public var body: some View {
#if os(iOS)
TextField(
"0",
value: $amount,
format: .currency(code: currency.identifier)
)
.keyboardType(.decimalPad)
.textFieldStyle(.default)
#else
TextField(
"0",
value: $amount,
format: .currency(code: currency.identifier)
)
.textFieldStyle(.default)
#endif
}
}
42 changes: 42 additions & 0 deletions Sources/OversizeUI/Controls/URLField/URLField.swift
@@ -0,0 +1,42 @@
//
// Copyright © 2023 Alexander Romanov
// URLField.swift
//

import SwiftUI

#if os(iOS)
@available(iOS 15.0, *)
@available(macOS, unavailable)
@available(watchOS, unavailable)
@available(tvOS, unavailable)
public struct URLField: View {
@Binding private var url: URL?
@State private var urlString: String = ""
let title: String

@State private var textFieldHelper: FieldHelperStyle = .none

public init(_ title: String = "URL", url: Binding<URL?>) {
self.title = title
_url = url
}

public var body: some View {
if #available(iOS 16.0, *) {
TextField(title, value: $url, format: .url)
.keyboardType(.URL)
.textContentType(.URL)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
} else {
TextField(title, text: $urlString)
.keyboardType(.URL)
.textContentType(.URL)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.fieldHelper(.constant("Invalid URL"), style: $textFieldHelper)
}
}
}
#endif
102 changes: 102 additions & 0 deletions Sources/OversizeUI/Core/ViewModifier/HalfSheet/HalfSheet.swift
@@ -0,0 +1,102 @@
//
// Copyright © 2022 Alexander Romanov
// HalfSheet.swift, created on 30.12.2022
//

import SwiftUI
#if canImport(UIKit)
import UIKit
#endif

public enum Detents: Hashable {
case large
case medium
case height(CGFloat)

#if os(iOS)
@available(iOS 15, *)
public var uiViewDetents: UISheetPresentationController.Detent {
switch self {
case .large:
return .large()
case .medium:
return .medium()
case let .height(height):
return height > 560 ? .large() : .medium()
}
}

@available(iOS 16, *)
func convertToSUI() -> PresentationDetent {
switch self {
case .large:
return PresentationDetent.large
case .medium:
return PresentationDetent.medium
case let .height(height):
return PresentationDetent.height(height)
}
}
#endif
}

// swiftlint:disable line_length
#if os(iOS)

public struct SheetModifier: ViewModifier {
public let detents: [Detents]
public func body(content: Content) -> some View {
SheetView(detents: detents) {
content
}
}
}

public extension View {
@_disfavoredOverload
func presentationDetents(_ detents: [Detents]) -> some View {
Group {
if #available(iOS 16, *) {
let suiDetents: Set<PresentationDetent> = Set(detents.compactMap { $0.convertToSUI() })
self.presentationDetents(suiDetents)
} else {
modifier(SheetModifier(detents: detents))
}
}
}
}

public struct SheetView<Content: View>: UIViewControllerRepresentable {
private let content: Content
private let detents: [Detents]

public init(detents: [Detents], @ViewBuilder content: () -> Content) {
self.content = content()
self.detents = detents
}

public func makeUIViewController(context _: Context) -> SheetHostingController<Content> {
SheetHostingController(rootView: content, detents: detents.map { $0.uiViewDetents })
}

public func updateUIViewController(_: SheetHostingController<Content>, context _: Context) {}
}

public final class SheetHostingController<Content: View>: UIHostingController<Content> {
var detents: [UISheetPresentationController.Detent] = []

override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let controller = sheetPresentationController {
controller.detents = detents
}
}
}

public extension SheetHostingController {
convenience init(rootView: Content, detents: [UISheetPresentationController.Detent]) {
self.init(rootView: rootView)
self.detents = detents
}
}
#endif

0 comments on commit f83c87e

Please sign in to comment.