Skip to content

Commit

Permalink
Merge pull request #24 from futuredapp/feature/v1.0.0
Browse files Browse the repository at this point in the history
Release - v1.0.0
ssestak authored Jul 25, 2024
2 parents 6e10154 + 41359ee commit 775748d
Showing 59 changed files with 1,403 additions and 330 deletions.
7 changes: 2 additions & 5 deletions .github/workflows/macos-12.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: macOS 12
name: macos latest

on:
push:
@@ -10,13 +10,10 @@ on:

jobs:
test:
runs-on: macos-12
runs-on: macos-latest

steps:
- uses: actions/checkout@v2
- name: Lint
run: |
swiftlint --strict
- name: Swift build & test
run: |
swift build
2 changes: 1 addition & 1 deletion .swiftpm/xcode/xcshareddata/xcschemes/FuturedKit.xcscheme
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
LastUpgradeVersion = "1530"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
26 changes: 17 additions & 9 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
// swift-tools-version:5.6
// swift-tools-version:5.7.1

import PackageDescription

let package = Package(
name: "FuturedKit",
platforms: [
.iOS(.v13),
.macOS(.v10_15),
.watchOS(.v6),
.tvOS(.v13)
.iOS(.v16),
.macOS(.v13),
.watchOS(.v9),
.tvOS(.v16)
],
products: [
.library(
name: "FuturedKit",
targets: ["FuturedKit"]
name: "FuturedArchitecture",
targets: ["FuturedArchitecture"]
),
.library(
name: "FuturedHelpers",
targets: ["FuturedHelpers"]
)
],
dependencies: [
@@ -22,15 +26,19 @@ let package = Package(
],
targets: [
.target(
name: "FuturedKit",
name: "FuturedArchitecture"
),
.target(
name: "FuturedHelpers",
dependencies: [
"FuturedArchitecture",
.product(name: "BindingKit", package: "BindingKit"),
.product(name: "CollectionConcurrencyKit", package: "CollectionConcurrencyKit")
]
),
.testTarget(
name: "FuturedKitTests",
dependencies: ["FuturedKit"]
dependencies: ["FuturedArchitecture", "FuturedHelpers"]
)
]
)
40 changes: 30 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -4,27 +4,47 @@ SwiftUI state management tools, resources and views used by Futured.

## Features

- State management
- `Resource` modelling states for asynchronously loaded data
including errors, loading and refreshing.
- `ViewState` typealias representing view state.
- Views
- `AnyShape` type-erased view for cases where you need to change the shape
easily by using ternary operator.

### Architecture

- ``Coordinator``
- The Coordinator protocol defines a destination type (instances are hashable and identifiable), root view and destination views that conform to the `View` protocol, and properties for a sheet, fullscreen cover and alert model. Furthermore, it provides methods for presenting/dismissing sheets and fullscreen covers, displaying alerts, handling the dismissal of sheets and fullscreen covers.
- ``TabCoordinator``
- The `TabCoordinator` protocol extends the `Coordinator` protocol and provides additional functionality for managing tab views in SwiftUI apps. It introduces a `Tab` associated type and a `selectedTab` property which is used for managing the currently selected tab.
- ``NavigationStackCoordinator``
- The `NavigationStackCoordinator` protocol also extends the `Coordinator` protocol and adds additional functionality for managing navigation stacks in SwiftUI apps. It manages a `path` which represents the array of navigational elements in the navigation stack, and provides methods for moving through this navigation stack. Actions such as navigate (to push a new view onto the stack), pop (to remove the current view from the stack), and pop to a specific destination in the stack are defined in this protocol.
- ``Component``
- Components are the views that users interact with directly in the application. They handle the user interface and its functionality. From buttons to table views, every element a user sees and interacts with is a separate View in our application. Our aim here is to keep our Components as simple and clean as possible to provide a clutter-free and intuitive user interface.
- Components are designed to hold multiple view elements which can be reusable in other components. When a user interacts with a component action view (button, text field delegates, etc.), they call the ComponentModel functions for the desired behavior.
- ``ComponentModel``
- ComponentModels act as an intermediary between Components and Models. In our iOS application, they handle the business logic and are in charge of making API calls, parsing data, managing and performing computations. Typically, the ComponentModel will format the data it receives from the Model so that it's ready to be presented by the View.
- ``DataCacheModel``
- DataCacheModel is a component in our application's architecture whose function is to store and retrieve data in a fast and efficient manner. It saves user information or data from server responses, which can then be readily accessed when needed. The advantage of DataCacheModel is that it reduces the need for repetitive network calls, providing a smoother user experience.
- Use DataCacheModel for every DataModel which needs to be stored for multiple app flows or for app flows which can be reopened. For use cases like creating a new DataModel which is specific for one flow, create a new DataCache wrapper that wraps the DataModel in this specific flow coordinator.

### Views

- ``AnyShape``
- ``WrappedUIImagePicker``
- ``CameraImagePicker``
- ``GalleryImagePicker``

### Alert presentation

- ``AlertModel``

## Installation

When using Swift package manager install using or add following line to your dependencies:

```swift
.package(url: "https://github.com/futuredapp/FuturedKit.git", from: "0.4.0")
.package(url: "https://github.com/futuredapp/FuturedKit.git", from: "1.0.0")
```

## Contributions

All contributions are welcome.

Current maintainer and main contributor is [Matěj Kašpar Jirásek](https://github.com/mkj-is), <matej.jirasek@futured.app>.
Current maintainer and main contributor is [Ievgen Samoilyk](https://github.com/samoilyk), <ievgen.samoilyk@futured.app>.

## License

87 changes: 87 additions & 0 deletions Sources/FuturedArchitecture/Architecture/Alert/AlertModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import SwiftUI

/// A model representation of an alert, which is used by `defaultAlert(model: AlertModel)` SwiftUI View modifier
///
/// ## Overview
///
/// It wrappes the native `alert(_:isPresented:presenting:actions:message:)`, but you show an alert in different way by using the `defaultAlert(model:)` view modifier,
/// which then appears whenever the bound `model` value is not `nil` value.
/// Alert model contains two actions: `primaryAction` and `secondaryAction`, which are then represented as SwiftUI Button
/// If both values are nil, system presents alert with standard "OK" button and given `title` and `message`
/// If one of the actions contains `destructive` button role and there is no `cancel` button role, system will add standard button with "Cancel" title.
/// If both `primaryAction` and `secondaryAction` actions have `destructive` button roles, system will still add standard "Cancel" button to the alert.
///
/// In the following example, a button presents a simple alert when tapped, by updating a local `alertModel` property.
/// Alert contains given `title` and `message` with standard "OK" button
///
/// ```swift
/// @State private var alertModel: AlertModel?
/// var body: some View {
/// Button("Tap to show alert") {
/// alertModel = AlertModel(
/// title: "Current Location Not Available",
/// message: "Your current location can’t be determined at this time."
/// )
/// }
/// .defaultAlert(model: $alertModel)
/// }
/// ```
///
/// The following example adds primary “Delete” button with `destructive` button role and standard "Cancel" button which is added by system.
///
/// ```swift
/// @State private var alertModel: AlertModel?
/// var body: some View {
/// Button("Tap to show alert") {
/// alertModel = AlertModel(
/// title: "Unable to Save Workout Data",
/// message: "The connection to the server was lost.",
/// primaryAction: AlertModel.ButtonAction(
/// title: "Delete,
/// buttonRole: .destructive,
/// action: deleteWorkoutData
/// )
/// )
/// }
/// .defaultAlert(model: $alertModel)
/// }
/// ```
///
/// The alert handles its own dismissal when the user taps one of the buttons in the alert,
/// by setting the bound `model` value back to `nil`.
public struct AlertModel: Identifiable {
public struct ButtonAction {
let title: String
let buttonRole: ButtonRole?
let action: () -> Void

public init(title: String, buttonRole: ButtonRole? = nil, action: @escaping () -> Void) {
self.title = title
self.buttonRole = buttonRole
self.action = action
}
}

public var id: String? {
title + (message ?? "")
}

let title: String
let message: String?
let primaryAction: ButtonAction?
let secondaryAction: ButtonAction?

/// Creates an alert model.
/// - Parameters:
/// - title: The title of the alert.
/// - message: The message to display in the body of the alert.
/// - primaryAction: The specification of the alert primary action.
/// - secondaryAction: The specification of the alert secondary action.

public init(title: String, message: String?, primaryAction: ButtonAction? = nil, secondaryAction: ButtonAction? = nil) {
self.title = title
self.message = message
self.primaryAction = primaryAction
self.secondaryAction = secondaryAction
}
}
75 changes: 75 additions & 0 deletions Sources/FuturedArchitecture/Architecture/Alert/AlertModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import SwiftUI

private struct AlertModifier: ViewModifier {
@Binding var model: AlertModel?

init(_ model: Binding<AlertModel?>) {
self._model = model
}

func body(content: Content) -> some View {
content.alert(
model?.title ?? "",
isPresented: .init(
get: { model != nil },
set: { isPresented, _ in
if !isPresented {
model = nil
}
}
),
presenting: model,
actions: { model in
if let primaryAction = model.primaryAction {
Button(
primaryAction.title,
role: primaryAction.buttonRole,
action: primaryAction.action
)

if let secondaryAction = model.secondaryAction {
Button(
secondaryAction.title,
role: secondaryAction.buttonRole,
action: secondaryAction.action
)
}
}
},
message: { model in
if let message = model.message {
Text(message)
}
}
)
}
}

extension View {
/// Presents an alert to the user.
/// - Parameters:
/// - model: A binding to a `AlertModel` value that determines whether to present the alert
/// that you define by this model.
///
/// ## Overview
///
/// Use this method when you need to show simplified an alert to the user.
/// In the following example, a button presents an alert when tapped, by updating a local `alertModel` property.
/// Alert contains given `title` and `message` with standard "OK" button
///
/// ```swift
/// @State private var alertModel: AlertModel?
/// var body: some View {
/// Button("Tap to show alert") {
/// alertModel = AlertModel(
/// title: "Current Location Not Available",
/// message: "Your current location can’t be determined at this time."
/// )
/// }
/// .defaultAlert(model: $alertModel)
/// }
/// ```
public func defaultAlert(model: Binding<AlertModel?>) -> some View {
modifier(AlertModifier(model))
}
}
7 changes: 7 additions & 0 deletions Sources/FuturedArchitecture/Architecture/ComponentModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

public protocol ComponentModel: ObservableObject {
associatedtype Event

var onEvent: (Event) -> Void { get }
}
Loading

0 comments on commit 775748d

Please sign in to comment.