Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Feature/refactoring #3

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 3 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// swift-tools-version:5.1
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "ReMVVMExt",
platforms: [.iOS(.v13)],
platforms: [.iOS(.v14)],
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
Expand All @@ -25,7 +25,7 @@ let package = Package(
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "ReMVVMExt",
dependencies: ["ReMVVMCore", "ReMVVMSwiftUI"],
dependencies: [.product(name: "ReMVVMCore", package: "ReMVVM"), .product(name: "ReMVVMSwiftUI", package: "ReMVVM")],
path: "Sources",
exclude: [])
]
Expand Down
43 changes: 31 additions & 12 deletions Sources/Actions/NavigationActions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,17 @@ public struct Push: StoreAction {
}

public struct Pop: StoreAction {
public init() { }
} //TODO: POP Type - to root, number of pop items
public enum PopMode {
case popToRoot, pop(Int)
}

public let animated: Bool
public let mode: PopMode
public init(mode: PopMode = .pop(1), animated: Bool = true) {
self.mode = mode
self.animated = animated
}
}

public struct Show: StoreAction {
public let viewFactory: ViewFactory
Expand All @@ -34,38 +43,48 @@ public struct Show: StoreAction {
view: @autoclosure @escaping () -> V,
factory: ViewModelFactory? = nil,
animated: Bool = true, // TODO
navigationBarHidden: Bool = true) // TODO
navigationBarHidden: Bool = true)
where N: CaseIterableNavigationItem, V: View {

self.viewFactory = { AnyView(view()) }
self.viewFactory = { AnyView(view().navigationBarHidden(navigationBarHidden)) }
self.factory = factory
self.item = navigationItem
}
}

public struct ShowModal: StoreAction {
public enum PresentationStyle {
case sheet
case fullScreenCover
}

public let viewFactory: ViewFactory
public let factory: ViewModelFactory?
public let navigation: Bool

public let presentationStyle: PresentationStyle

public init<V>(view: @autoclosure @escaping () -> V,
factory: ViewModelFactory? = nil,
navigation: Bool = false)
navigation: Bool = false,
presentationStyle: PresentationStyle = .fullScreenCover)
//TODO: animated ?
//TODO: modal type (fullscreen)
//TODO: navigation included?

where V: View {

self.viewFactory = { AnyView(view()) }
self.factory = factory
self.navigation = navigation
self.presentationStyle = presentationStyle
}
}

public struct DismissModal: StoreAction {
public init() { }
public enum Mode {
case dismiss(Int)
case all
}

public let mode: Mode
public init(mode: Mode = .dismiss(1)) {
self.mode = mode
}
}

public struct Synchronize: StoreAction {
Expand Down
23 changes: 23 additions & 0 deletions Sources/Helpers/Binding.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// File.swift
//
//
// Created by Paweł Zgoda-Ferchmin on 01/06/2022.
//

import Combine
import SwiftUI

extension Binding {
func map<T>(get: @escaping (Value) -> T?, set: @escaping (T?) -> Value?) -> Binding<T?> {
Binding<T?>(get: { get(wrappedValue) },
set: { wrappedValue = set($0) ?? wrappedValue })
}
}

extension Array {
func with(_ index: Array.Index?) -> Array.Element? {
guard let index = index, indices.contains(index) else { return nil }
return self[index]
}
}
2 changes: 1 addition & 1 deletion Sources/Helpers/View+AnyView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
import SwiftUI

extension View {
public var any: AnyView { return AnyView(self) }
public var any: AnyView { AnyView(self) }
}
2 changes: 1 addition & 1 deletion Sources/NavigationItem/NavigationItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ extension NavigationItem where Self: CaseIterable {

extension NavigationItem where Self: Equatable {
public func isEqual(to item: NavigationItem) -> Bool {
return (item as? Self) == self
(item as? Self) == self
}
}

Expand Down
9 changes: 4 additions & 5 deletions Sources/NavigationItem/TabNavigationItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,19 @@
// Copyright © 2021 Dariusz Grzeszczak. All rights reserved.
//

import SwiftUI
import ReMVVMCore
import SwiftUI

public typealias HashableTabNavigationItem = TabNavigationItem & Hashable

public protocol TabNavigationItem: NavigationItem {
var action: StoreAction { get }
var tabItemFactory: ViewFactory { get }

var tabViewFactory: ViewFactory { get }
var tabItemFactory: (Bool) -> AnyView { get }
var tabViewFactory: () -> AnyView { get }
var viewModelFactory: ViewModelFactory? { get }
}

extension TabNavigationItem where Self: CaseIterableNavigationItem {
extension TabNavigationItem where Self: CaseIterableNavigationItem {
public var action: StoreAction { Show(on: self, view: tabViewFactory()) }
public var viewModelFactory: ViewModelFactory? { nil }
public var title: String? { nil }
Expand Down
41 changes: 30 additions & 11 deletions Sources/ReMVVMExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,33 @@
//

import ReMVVMCore
import SwiftUI

private enum AppNavigationReducer<State, R>: Reducer where R: Reducer, R.State == State, R.Action == StoreAction {

static func reduce(state: AppNavigationState<State>, with action: StoreAction) -> AppNavigationState<State> {
AppNavigationState<State>(
appState: R.reduce(state: state.appState, with: action),
navigation: NavigationReducer.reduce(state: state.navigation, with: action)
)
AppNavigationState<State>(appState: R.reduce(state: state.appState, with: action),
navigation: NavigationReducer.reduce(state: state.navigation, with: action),
uiConfig: state.uiConfig)
}
}

extension ReMVVM {

public static func initialize<State, R>(with state: State,
stateMappers: [StateMapper<State>] = [],
reducer: R.Type,
middleware: [AnyMiddleware] = []) -> Store<AppNavigationState<State>>
stateMappers: [StateMapper<State>] = [],
uiStateConfig: UIStateConfig,
reducer: R.Type,
middleware: [AnyMiddleware] = []) -> Store<AppNavigationState<State>>
where R: Reducer, R.Action == StoreAction, R.State == State {

let mappers: [StateMapper<AppNavigationState<State>>] = [
StateMapper(for: \.appState),
StateMapper(for: \.navigation)
StateMapper(for: \.navigation),
StateMapper(for: \.uiConfig)
]

let stateMappers = mappers + stateMappers.map { $0.map(with: \.appState) }

return self.initialize(with: AppNavigationState(appState: state),
return self.initialize(with: AppNavigationState(appState: state, uiConfig: uiStateConfig),
stateMappers: stateMappers,
reducer: AppNavigationReducer<State, R>.self,
middleware: middleware)
Expand All @@ -54,3 +54,22 @@ extension ReMVVM {
return store
}
}


public struct UIStateConfig {
let navigationConfig: NavigationConfig?

public init(navigationConfig: NavigationConfig?) {
self.navigationConfig = navigationConfig
}
}

public struct NavigationConfig {
public typealias TabBarFactory = (_ items: [TabNavigationItem], _ selectedIndex: Binding<Int?>) -> AnyView

public var tabBarFactory: TabBarFactory

public init<V>(tabBarFactory: @escaping (_ items: [TabNavigationItem], _ selectedIndex: Binding<Int?>) -> V) where V: View {
self.tabBarFactory = { tabBarFactory($0, $1).any }
}
}
18 changes: 14 additions & 4 deletions Sources/Reducers/DismissModalReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,27 @@
//

import ReMVVMCore
import UIKit

public enum DismissModalReducer: Reducer {
public static func reduce(state: Navigation, with action: DismissModal) -> Navigation {
Navigation(root: state.root, modals: state.modals.dropLast())
switch action.mode {
case .dismiss(let count):
return Navigation(root: state.root, modals: state.modals.dropLast(count))
case .all:
return Navigation(root: state.root, modals: state.modals.dropAll())
}
}
}

extension Stack where StackItem == Modal {
func dropLast(_ count: Int) -> Self {
guard !items.isEmpty else { return self }
guard items.count >= count else { return Stack(with: items.dropLast(items.count)) }
return Stack(with: items.dropLast(count))
}

func dropLast() -> Self {
guard items.count > 0 else { return self }
return Stack(with: items.dropLast())
func dropAll() -> Self {
Stack(with: [])
}
}
3 changes: 1 addition & 2 deletions Sources/Reducers/NavigationReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import ReMVVMCore

public enum NavigationReducer: Reducer {

static let composed = PushReducer
.compose(with: PopReducer.self)
.compose(with: ShowReducer.self)
Expand All @@ -18,6 +17,6 @@ public enum NavigationReducer: Reducer {
.compose(with: SynchronizeReducer.self)

public static func reduce(state: Navigation, with action: StoreAction) -> Navigation {
return composed.reduce(state: state, with: action)
composed.reduce(state: state, with: action)
}
}
63 changes: 44 additions & 19 deletions Sources/Reducers/PopReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,27 @@ import ReMVVMCore

public enum PopReducer: Reducer {
public static func reduce(state: Navigation, with action: Pop) -> Navigation {
state.pop()
switch action.mode {
case .pop(let count): return state.pop(count)
case .popToRoot: return state.popToRoot()
}
}
}

extension Navigation {
func pop(_ count: Int) -> Navigation {
if modals.hasNavigation {
return Navigation(root: root, modals: modals.pop(count))
} else {
return Navigation(root: root.pop(count))
}
}

func pop() -> Navigation {
func popToRoot() -> Navigation {
if modals.hasNavigation {
return Navigation(root: root, modals: modals.pop())
return Navigation(root: root, modals: modals.popToRoot())
} else {
return Navigation(root: root.pop())
return Navigation(root: root.popToRoot())
}
}
}
Expand All @@ -29,40 +39,55 @@ extension Stack where StackItem == Modal {

var hasNavigation: Bool { items.contains { $0.hasNavigation } }

func pop() -> Self {
func pop(_ count: Int) -> Self {
guard hasNavigation else { return self }
let items = self.items.reversed().drop { !$0.hasNavigation }.reversed()
guard case .navigation(let stack) = items.last else { return self }
let newItems = Array(items.dropLast()) + [.navigation(stack.pop(count))]
return Stack(with: newItems)
}

func popToRoot() -> Self {
guard hasNavigation else { return self }
let items = self.items.reversed().drop { !$0.hasNavigation }.reversed()
guard case .navigation(let stack) = items.last else { return self }
let newItems = Array(items.dropLast()) + [.navigation(stack.pop())]
let newItems = Array(items.dropLast()) + [.navigation(stack.popToRoot())]
return Stack(with: newItems)
}
}

extension Root {
func pop(_ count: Int) -> Root {
let stacks = self.stacks.enumerated().map { index, stack -> (NavigationItem, Stack<Element>) in
guard index == current else { return stack }
return (stack.0, stack.1.pop(count))
}
return Root(current: current, stacks: stacks)
}

func pop() -> Root {
func popToRoot() -> Root {
let stacks = self.stacks.enumerated().map { index, stack -> (NavigationItem, Stack<Element>) in
guard index == current else { return stack }
return (stack.0, stack.1.pop())
return (stack.0, stack.1.popToRoot())
}
return Root(current: current, stacks: stacks)
}
}

extension Stack where StackItem == Element {

func pop() -> Self {
func pop(_ count: Int) -> Self {
guard items.count > 1 else { return self }
return Stack(with: items.dropLast())
guard items.count > count else { return Stack(with: items.dropLast(items.count - 1)) }
return Stack(with: items.dropLast(count))
}

func empty() -> Self {
guard items.count > 0 else { return self }
return Stack(id: items[0].id)
}

// func popToRoot() -> Self {
// guard items.count > 1 else { return self }
// return Stack(with: [items[0]])
// func empty() -> Self {
// guard items.count > 0 else { return self }
// return Stack(id: items[0].id)
// }

func popToRoot() -> Self {
guard items.count > 1 else { return self }
return Stack(with: [items[0]])
}
}
Loading