Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
public extension String {
/// Represents the empty string `""`.
static let empty = ""
/// Represents the new line string .
static let newLine = "\n"

/**
Crops the string to a given number of characters, truncating the rest and replacing the last 3 characters with '...'.
Expand Down
18 changes: 18 additions & 0 deletions Sources/INCommons/SwiftUI/ApplicationManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import UIKit

/// The interface of the `ApplicationManager` which can be used to inject different implementations.
/// Normally code should not be directly dependent upon the `UIApplication` because that might break UnitTests
/// when running the tests on a device / simulator which differs from the test's expectations.
/// Therefore, code should get the `UIApplication` instance being injected and for tests it should be mocked.
/// It's expected that the implementation informs the `ObservableObject` of any value changes.
@MainActor
public protocol ApplicationManager {
/// A boolean which refers `isIdleTimerDisabled`on UIApplication
var isScreenLockEnabled: Bool { get }

/// Disables screen dimming automatically
func disableScreenLock()

/// Enables screen dimming automatically
func enableScreenLock()
}
20 changes: 20 additions & 0 deletions Sources/INCommons/SwiftUI/ApplicationManagerLogic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import UIKit

/// A concrete implementation of the `ApplicationManager` protocol which can be used in app code
/// to return the corresponding values from `UIApplication`.
@available(iOSApplicationExtension, unavailable)
public final class ApplicationManagerLogic: ApplicationManager {
public var isScreenLockEnabled: Bool {
!UIApplication.shared.isIdleTimerDisabled
}

public func disableScreenLock() {
UIApplication.shared.isIdleTimerDisabled = true
}

public func enableScreenLock() {
UIApplication.shared.isIdleTimerDisabled = false
}

public init() {}
}
53 changes: 53 additions & 0 deletions Sources/INCommons/SwiftUI/ColorPickerButton.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import SwiftUI

public struct ColorPickerButton: View {
enum Constants {
static let colorButtonSize: CGSize = .init(width: 36, height: 36)
static let colorButtonInnerCircleSize: CGSize = .init(width: 26, height: 26)
static let colorButtonEndAngle: CGFloat = 360
static let colorButtonStrokeWidth: CGFloat = 3
static let colorButtonGradient = Gradient(
colors: [
Color(uiColor: UIColor(hex: "E7E040") ?? .clear),
Color(uiColor: UIColor(hex: "EEAA3C") ?? .clear),
Color(uiColor: UIColor(hex: "E8403B") ?? .clear),
Color(uiColor: UIColor(hex: "B33ED5") ?? .clear),
Color(uiColor: UIColor(hex: "694AE8") ?? .clear),
Color(uiColor: UIColor(hex: "3CCAE7") ?? .clear),
Color(uiColor: UIColor(hex: "3CE885") ?? .clear),
Color(uiColor: UIColor(hex: "89E743") ?? .clear),
Color(uiColor: UIColor(hex: "E7E040") ?? .clear)
]
)
}

private let color: Color
private let action: () -> Void
public init(color: Color, action: @escaping () -> Void) {
self.color = color
self.action = action
}

public var body: some View {
Button {
action()
} label: {
ZStack {
Circle()
.frame(size: Constants.colorButtonInnerCircleSize)
.foregroundStyle(color)
Circle()
.strokeBorder(
AngularGradient(
gradient: Constants.colorButtonGradient,
center: .center,
startAngle: .zero,
endAngle: .degrees(Constants.colorButtonEndAngle)
),
lineWidth: Constants.colorButtonStrokeWidth
)
}
.frame(size: Constants.colorButtonSize)
}
}
}
88 changes: 88 additions & 0 deletions Sources/INCommons/SwiftUI/ColorPickerSheet.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import SwiftUI

public extension View {
func colorPickerSheet(
isPresented: Binding<Bool>,
selection: Binding<Color>,
supportsAlpha: Bool = true,
title: String? = nil,
animated: Bool = true
) -> some View {
background(
ColorPickerSheet(
isPresented: isPresented,
selection: selection,
supportsAlpha: supportsAlpha,
title: title,
animated: animated
)
)
}
}

private struct ColorPickerSheet: UIViewRepresentable {
@Binding var isPresented: Bool
@Binding var selection: Color
let supportsAlpha: Bool
let title: String?
let animated: Bool
func makeCoordinator() -> Coordinator {
Coordinator(selection: $selection, isPresented: $isPresented)
}

class Coordinator: NSObject, UIColorPickerViewControllerDelegate, UIAdaptivePresentationControllerDelegate {
@Binding var selection: Color
@Binding var isPresented: Bool
var didPresent = false

init(selection: Binding<Color>, isPresented: Binding<Bool>) {
_selection = selection
_isPresented = isPresented
}

func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
selection = Color(viewController.selectedColor)
}

func colorPickerViewControllerDidFinish(_: UIColorPickerViewController) {
isPresented = false
didPresent = false
}

func presentationControllerDidDismiss(_: UIPresentationController) {
isPresented = false
didPresent = false
}
}

func getTopViewController(from view: UIView) -> UIViewController? {
guard var top = view.window?.rootViewController else {
return nil
}
while let next = top.presentedViewController {
top = next
}
return top
}

func makeUIView(context _: Context) -> UIView {
let view = UIView()
view.isHidden = true
return view
}

func updateUIView(_ uiView: UIView, context: Context) {
if isPresented, !context.coordinator.didPresent {
let modal = UIColorPickerViewController()
modal.sheetPresentationController?.detents = [.medium(), .large()]
modal.selectedColor = UIColor(selection)
modal.supportsAlpha = supportsAlpha
modal.title = title
modal.delegate = context.coordinator
modal.presentationController?.delegate = context.coordinator
let top = getTopViewController(from: uiView)
top?.present(modal, animated: true)
context.coordinator.didPresent = true
}
}
}
Loading