Skip to content

Commit

Permalink
Merge pull request #3 from mzapatae/feature/storage-provider
Browse files Browse the repository at this point in the history
Feature: Storage Provider
  • Loading branch information
MaikCL authored Jun 17, 2021
2 parents 685a537 + 170fbcc commit 63986d7
Show file tree
Hide file tree
Showing 22 changed files with 674 additions and 37 deletions.
1 change: 0 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ let package = Package(
products: [
.library(
name: "Altair-MDK",
type: .dynamic,
targets: [
"AltairMDKCommon",
"AltairMDKProviders"
Expand Down
26 changes: 26 additions & 0 deletions Sources/Common/Extensions/Combine/Publisher+NoRetain.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// Publisher+NoRetain.swift
//
//
// Created by Miguel Angel on 10-05-21.
//

import Combine

extension Publisher where Self.Failure == Never {

/// ### Example of use
/// store
/// .$state
/// .map { $0.exception }
/// .receive(on: DispatchQueue.main)
/// .assignNoRetain(to: \.exception, on: self)
/// .store(in: &bag)
///
public func assignNoRetain<Root>(to keyPath: ReferenceWritableKeyPath< Root,
Self.Output >, on object: Root) -> AnyCancellable where Root: AnyObject {
sink { [weak object] (value) in
object?[keyPath: keyPath] = value
}
}
}
5 changes: 2 additions & 3 deletions Sources/Common/Extensions/Combine/Publisher+Store.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ extension Publishers {

public static func store<State, Action, Scheduler: Combine.Scheduler>(
initial: State,
input: PassthroughSubject<Action, Never>,
reduce: @escaping (State, Action) -> State,
scheduler: Scheduler,
sideEffects: [SideEffect<Action>]
sideEffects: [SideEffect<State, Action>]
) -> AnyPublisher<State, Never> {

let state = CurrentValueSubject<State, Never>(initial)
let actions = sideEffects.map { $0.run(input.eraseToAnyPublisher()) }
let actions = sideEffects.map { $0.run(state.eraseToAnyPublisher()) }

return Deferred {
Publishers.MergeMany(actions)
Expand Down
84 changes: 84 additions & 0 deletions Sources/Common/Extensions/SwiftUI/View+Navigation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// View+Navigation.swift
//
//
// Created by Miguel Angel on 07-05-21.
//

import SwiftUI

extension View {

public func onNavigation(_ action: @escaping () -> Void) -> some View {
let isActive = Binding(
get: { false },
set: { newValue in
if newValue {
action()
}
}
)
return NavigationLink(destination: EmptyView(), isActive: isActive) {
self
}
}

/// ### Example of use
/// struct ListingScene: View {
/// @ObservedObject private(set) var viewModel: ListingViewModel
///
/// @State private var destination: Destination?
///
/// var body: some View {
/// NavigationView {
/// VStack {
/// Button("Nav") {
/// destination = .detail(id: "")
/// }.navigation(item: $destination, destination: viewModel.router.route(to:))
/// }
/// }
/// .navigationViewStyle(StackNavigationViewStyle())
/// }
/// }
///
public func navigation<Item, Destination: View>(item: Binding<Item?>, @ViewBuilder destination: (Item) -> Destination) -> some View {
let isActive = Binding(
get: { item.wrappedValue != nil },
set: { value in
if !value {
item.wrappedValue = nil
}
}
)
return navigation(isActive: isActive) {
item.wrappedValue.map(destination)
}
}

public func navigation<Destination: View>(isActive: Binding<Bool>, @ViewBuilder destination: () -> Destination) -> some View {
overlay(
NavigationLink(
destination: isActive.wrappedValue ? destination() : nil,
isActive: isActive,
label: { EmptyView() }
)
)
}

}

extension NavigationLink {

public init<T: Identifiable, D: View>(item: Binding<T?>, @ViewBuilder destination: @escaping (T) -> D, @ViewBuilder label: () -> Label) where Destination == D? {
let isActive = Binding(
get: { item.wrappedValue != nil },
set: { value in
if !value {
item.wrappedValue = nil
}
}
)
self.init(destination: item.wrappedValue.map(destination), isActive: isActive, label: label)
}

}
1 change: 1 addition & 0 deletions Sources/Common/Protocols/Exception.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ public protocol Exception: LocalizedError {
public enum ExceptionCategory {
case feature
case network
case storage
case mappers
}
18 changes: 16 additions & 2 deletions Sources/Common/Protocols/Mappers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,25 @@ import Foundation
public protocol ModelMapper {
associatedtype Entity
associatedtype Model
static var mapEntityToModel: (Entity) throws -> Model { get }
static var mapEntityToModel: (Entity) -> Model { get }
}

/// Map from Data or Presentation Model to Entity
/// Map from Data or Presentation Model to Domain Entity
public protocol EntityMapper {
associatedtype Model
associatedtype Entity
static var mapModelToEntity: (Model) -> Entity { get }
}

/// Map from Domain Entity to Data or Presentation Model
public protocol ModelFailableMapper {
associatedtype Entity
associatedtype Model
static var mapEntityToModel: (Entity) throws -> Model { get }
}

/// Map from Data or Presentation Model to Domain Entity
public protocol EntityFailableMapper {
associatedtype Model
associatedtype Entity
static var mapModelToEntity: (Model) throws -> Entity { get }
Expand Down
14 changes: 13 additions & 1 deletion Sources/Common/Protocols/Store.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,23 @@
// Created by Miguel Angel on 25-03-21.
//

import SwiftUI
import Foundation

public protocol Store: ObservableObject {
associatedtype State
associatedtype Action
var state: State { get }
func trigger(action: Action)
func trigger(_ action: Action)
}

public extension Store {

func binding<Value>(for keyPath: KeyPath<State, Value>, transform: @escaping (Value) -> Action) -> Binding<Value> {
Binding<Value>(
get: { self.state[keyPath: keyPath] },
set: { self.trigger(transform($0)) }
)
}

}
10 changes: 0 additions & 10 deletions Sources/Common/Protocols/UseCase.swift

This file was deleted.

9 changes: 9 additions & 0 deletions Sources/Common/Types/Loadable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,21 @@ public protocol Functor {
associatedtype A
associatedtype B: Functor = Self

func map<C>(_ transform: (A) -> C) -> B where B.A == C
func tryMap<C>(_ transform: (A) throws -> C) throws -> B where B.A == C
}

extension Loadable: Functor {
public typealias A = T

public func map<C>(_ transform: (T) -> C) -> Loadable<C> {
switch self {
case .neverLoaded: return .neverLoaded
case .loading: return .loading
case .loaded(let value): return .loaded(transform(value))
}
}

public func tryMap<C>(_ transform: (T) throws -> C) throws -> Loadable<C> {
switch self {
case .neverLoaded: return .neverLoaded
Expand Down
10 changes: 5 additions & 5 deletions Sources/Common/Types/SideEffect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

import Combine

public struct SideEffect<Action> {
public let run: (AnyPublisher<Action, Never>) -> AnyPublisher<Action, Never>
public struct SideEffect<State, Action> {
public let run: (AnyPublisher<State, Never>) -> AnyPublisher<Action, Never>
}

extension SideEffect {
public init<Effect: Publisher>(effects: @escaping (Action) -> Effect) where Effect.Output == Action, Effect.Failure == Never {
self.run = { action -> AnyPublisher<Action, Never> in
action.map { effects($0) }.switchToLatest().eraseToAnyPublisher()
public init<Effect: Publisher>(effects: @escaping (State) -> Effect) where Effect.Output == Action, Effect.Failure == Never {
self.run = { state -> AnyPublisher<Action, Never> in
state.map { effects($0) }.switchToLatest().eraseToAnyPublisher()
}
}
}
37 changes: 37 additions & 0 deletions Sources/Common/Types/Toggleable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// Toggleable.swift
//
//
// Created by Miguel Angel on 12-05-21.
//

public enum Toggleable: Equatable {
case on
case off

public var isOn: Bool {
self == .on
}

public var isOff: Bool {
self == .off
}

public mutating func toggle() {
switch self {
case .on:
self = .off
case .off:
self = .on
}
}

public var state: Bool {
switch self {
case .on:
return true
case .off:
return false
}
}
}
25 changes: 25 additions & 0 deletions Sources/Common/Utils/Regex.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// RegexMatch.swift
//
//
// Created by Miguel Angel on 19-05-21.
//

import Foundation

public final class Regex {

public static func matches(for regex: String, in text: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex)
let results = regex.matches(in: text, range: NSRange(text.startIndex..., in: text))
return results.map {
String(text[Range($0.range, in: text)!])
}
} catch let error {
print("Invalid Regex: \(error.localizedDescription)")
return []
}
}

}
30 changes: 30 additions & 0 deletions Sources/Common/ViewModifiers/PopoverModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// PopoverModifier.swift
//
//
// Created by Miguel Angel on 07-05-21.
//

import SwiftUI

public struct PopoverModifier<Item: Identifiable, Destination: View>: ViewModifier {

// MARK: Stored Properties

private let item: Binding<Item?>
private let destination: (Item) -> Destination

// MARK: Initialization

public init(item: Binding<Item?>, @ViewBuilder content: @escaping (Item) -> Destination) {
self.item = item
self.destination = content
}

// MARK: Methods

public func body(content: Content) -> some View {
content.popover(item: item, content: destination)
}

}
30 changes: 30 additions & 0 deletions Sources/Common/ViewModifiers/SheetModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// SheetModifier.swift
//
//
// Created by Miguel Angel on 07-05-21.
//

import SwiftUI

public struct SheetModifier<Item: Identifiable, Destination: View>: ViewModifier {

// MARK: Stored Properties

private let item: Binding<Item?>
private let destination: (Item) -> Destination

// MARK: Initialization

public init(item: Binding<Item?>, @ViewBuilder content: @escaping (Item) -> Destination) {
self.item = item
self.destination = content
}

// MARK: Methods

public func body(content: Content) -> some View {
content.sheet(item: item, content: destination)
}

}
21 changes: 21 additions & 0 deletions Sources/Common/Views/LazyView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// LazyView.swift
//
//
// Created by Miguel Angel on 07-05-21.
//

import SwiftUI

public struct LazyView<Content: View>: View {
let build: () -> Content

public init(_ build: @autoclosure @escaping () -> Content) {
self.build = build
}

public var body: Content {
build()
}

}
Loading

0 comments on commit 63986d7

Please sign in to comment.