Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Version 1.0.1 #25

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 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
9 changes: 9 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@
"revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95",
"version" : "0.2.0"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git",
"state" : {
"revision" : "64889f0c732f210a935a0ad7cda38f77f876262d",
"version" : "509.1.1"
}
}
],
"version" : 2
Expand Down
24 changes: 21 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// swift-tools-version:5.7.1
// swift-tools-version:5.9

import PackageDescription
import CompilerPluginSupport

let package = Package(
name: "FuturedKit",
Expand All @@ -13,7 +14,10 @@ let package = Package(
products: [
.library(
name: "FuturedArchitecture",
targets: ["FuturedArchitecture"]
targets: [
"FuturedArchitecture",
"EnumIdentifiersGenerator"
]
),
.library(
name: "FuturedHelpers",
Expand All @@ -22,9 +26,23 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/mkj-is/BindingKit", from: "1.0.0"),
.package(url: "https://github.com/JohnSundell/CollectionConcurrencyKit", from: "0.1.0")
.package(url: "https://github.com/JohnSundell/CollectionConcurrencyKit", from: "0.1.0"),
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0")
],
targets: [
.target(
name: "EnumIdentifiersGenerator",
dependencies: [
"EnumIdentifiersGeneratorMacro"
]
),
.macro(
name: "EnumIdentifiersGeneratorMacro",
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
]
),
.target(
name: "FuturedArchitecture"
),
Expand Down
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ SwiftUI state management tools, resources and views used by Futured.
- ``CameraImagePicker``
- ``GalleryImagePicker``

### Alert presentation

- ``AlertModel``

## Installation

When using Swift package manager install using or add following line to your dependencies:
Expand Down
14 changes: 14 additions & 0 deletions Sources/EnumIdentifiersGenerator/EnumIdentifiersGenerator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// EnumIdentifiersGenerator.swift
//
//
// Created by Simon Sestak on 31/07/2024.
//

import Foundation

@attached(member, names: arbitrary)
public macro EnumIdentifiersGenerator() = #externalMacro(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EnumIdentifiersGenerator zvážil bych jiné jméno :) 🤔

module: "EnumIdentifiersGeneratorMacro",
type: "EnumIdentifiersGeneratorMacro"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//
// EnumIdentifiersGeneratorMacro.swift
//
//
// Created by Simon Sestak on 31/07/2024.
//

import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
import SwiftDiagnostics
import Foundation

public struct EnumIdentifiersGeneratorMacro: MemberMacro {
public static func expansion<Declaration, Context>(
of node: SwiftSyntax.AttributeSyntax,
providingMembersOf declaration: Declaration,
in context: Context) throws -> [SwiftSyntax.DeclSyntax] where Declaration : SwiftSyntax.DeclGroupSyntax, Context : SwiftSyntaxMacros.MacroExpansionContext {

guard let declaration = declaration.as(EnumDeclSyntax.self) else {
let enumError = Diagnostic(node: node._syntaxNode, message: Diagnostics.mustBeEnum)
context.diagnose(enumError)
return []
}

guard let enumCases: [SyntaxProtocol] = declaration.memberBlock
.children(viewMode: .fixedUp).filter({ $0.kind == .memberDeclList })
.first?
.children(viewMode: .fixedUp).filter({ $0.kind == SyntaxKind.memberDeclListItem })
.flatMap({ $0.children(viewMode: .fixedUp).filter({ $0.kind == .enumCaseDecl })})
.flatMap({ $0.children(viewMode: .fixedUp).filter({ $0.kind == .enumCaseElementList })})
.flatMap({ $0.children(viewMode: .fixedUp).filter({ $0.kind == .enumCaseElement })})
else {
let enumError = Diagnostic(node: node._syntaxNode, message: Diagnostics.mustHaveCases)
context.diagnose(enumError)
return []
}

let caseIds: [String] = enumCases.compactMap { enumCase in
guard let firstToken = enumCase.firstToken(viewMode: .fixedUp) else {
return nil
}

guard case let .identifier(id) = firstToken.tokenKind else {
return nil
}

return id
}

guard !caseIds.isEmpty else {
let enumError = Diagnostic(node: node._syntaxNode, message: Diagnostics.mustHaveCases)
context.diagnose(enumError)
return []
}

let modifier = declaration.hasPublicModifier ? "public " : ""
let enumID = "\(modifier)enum CaseID: String, Hashable, CaseIterable, CustomStringConvertible {\n\(caseIds.map { " case \($0)\n" }.joined())\n \(modifier)var description: String {\nself.rawValue\n }\n}"
let idAccessor = "\(modifier)var caseId: CaseID {\n switch self {\n\(caseIds.map { " case .\($0): .\($0)\n" }.joined()) }\n}"
let identifierVariable = "var id: String { self.caseId.rawValue }"
let hashableConformance = "func hash(into hasher: inout Hasher) {\nhasher.combine(id)\n}"
let comparableConformance = "static func == (lhs: Self, rhs: Self) -> Bool {\nlhs.id == rhs.id\n}"

return [
DeclSyntax(stringLiteral: enumID),
DeclSyntax(stringLiteral: idAccessor),
DeclSyntax(stringLiteral: identifierVariable),
DeclSyntax(stringLiteral: hashableConformance),
DeclSyntax(stringLiteral: comparableConformance)
]
}

public enum Diagnostics: String, DiagnosticMessage {

case mustBeEnum, mustHaveCases

public var message: String {
switch self {
case .mustBeEnum:
return "`@EnumIdentifiersGeneratorMacro` can only be applied to an `enum`"
case .mustHaveCases:
return "`@EnumIdentifiersGeneratorMacro` can only be applied to an `enum` with `case` statements"
}
}

public var diagnosticID: MessageID {
MessageID(domain: "EnumIdentifiersGeneratorMacro", id: rawValue)
}

public var severity: DiagnosticSeverity { .error }
}
}

private extension DeclGroupSyntax {
var hasPublicModifier: Bool {
self.modifiers.children(viewMode: .fixedUp)
.compactMap { syntax in
syntax.as(DeclModifierSyntax.self)?
.children(viewMode: .fixedUp)
.contains { syntax in
switch syntax.as(TokenSyntax.self)?.tokenKind {
case .keyword(.public):
return true
default:
return false
}
}
}
.contains(true)
}
}

@main
struct MacroPlugin: CompilerPlugin {
let providingMacros: [Macro.Type] = [
EnumIdentifiersGeneratorMacro.self,
]
}
19 changes: 14 additions & 5 deletions Sources/FuturedArchitecture/Architecture/Coordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ public protocol Coordinator: ObservableObject {
associatedtype RootView: View
associatedtype DestinationViews: View

/// `rootView` returns the coordinator's main view. Maintain its purity by defining only the view, without added logic or modifiers.
/// If logic or modifiers are needed, encapsulate them in a separate view that can accommodate necessary dependencies.
/// Skipping this recommendation may prevent UI updates when changing `@Published` properties, as `rootView` is static.
@ViewBuilder
static func rootView(with instance: Self) -> RootView

var sheet: Destination? { get set }
#if !os(macOS)
var fullscreenCover: Destination? { get set }
#endif
var alertModel: AlertModel? { get set }
var modalCover: ModalCoverModel<Destination>? { get set }

@ViewBuilder
func scene(for destination: Destination) -> DestinationViews
Expand All @@ -36,6 +35,16 @@ public extension Coordinator {
}

func onSheetDismiss() {}

var sheet: Destination? {
get { modalCover?.style == .sheet ? modalCover?.destination : nil }
set { modalCover = newValue.map { ModalCoverModel(destination: $0, style: .sheet) } }
}

var fullscreenCover: Destination? {
get { modalCover?.style == .fullscreenCover ? modalCover?.destination : nil }
set { modalCover = newValue.map { ModalCoverModel(destination: $0, style: .fullscreenCover) } }
}
}

#if !os(macOS)
Expand Down
22 changes: 22 additions & 0 deletions Sources/FuturedArchitecture/Architecture/ModalCoverModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// File.swift
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// File.swift
// ModalCoverModel.swift

//
//
// Created by Simon Sestak on 01/08/2024.
//

import Foundation

public struct ModalCoverModel<Destination: Hashable & Identifiable>: Identifiable {
let destination: Destination
let style: Style

enum Style {
case sheet
case fullscreenCover
}

public var id: Destination.ID {
destination.id
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ public struct NavigationStackFlow<Coordinator: NavigationStackCoordinator, Conte
content().navigationDestination(for: Coordinator.Destination.self, destination: coordinator.scene(for:))
}
.sheet(item: $coordinator.sheet, onDismiss: coordinator.onSheetDismiss, content: coordinator.scene(for:))
.defaultAlert(model: $coordinator.alertModel)
}
#else
public var body: some View {
Expand All @@ -24,7 +23,6 @@ public struct NavigationStackFlow<Coordinator: NavigationStackCoordinator, Conte
}
.sheet(item: $coordinator.sheet, onDismiss: coordinator.onSheetDismiss, content: coordinator.scene(for:))
.fullScreenCover(item: $coordinator.fullscreenCover, onDismiss: coordinator.onFullscreenCoverDismiss, content: coordinator.scene(for:))
.defaultAlert(model: $coordinator.alertModel)
}
#endif
}
2 changes: 0 additions & 2 deletions Sources/FuturedArchitecture/Architecture/TabViewFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ public struct TabViewFlow<Coordinator: TabCoordinator, Content: View>: View {
content()
}
.sheet(item: $coordinator.sheet, onDismiss: coordinator.onSheetDismiss, content: coordinator.scene(for:))
.defaultAlert(model: $coordinator.alertModel)
}
#else
public var body: some View {
Expand All @@ -24,7 +23,6 @@ public struct TabViewFlow<Coordinator: TabCoordinator, Content: View>: View {
}
.sheet(item: $coordinator.sheet, onDismiss: coordinator.onSheetDismiss, content: coordinator.scene(for:))
.fullScreenCover(item: $coordinator.fullscreenCover, onDismiss: coordinator.onFullscreenCoverDismiss, content: coordinator.scene(for:))
.defaultAlert(model: $coordinator.alertModel)
}
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,3 @@ SwiftUI architecture, resources and views used by Futured.
- ``WrappedUIImagePicker``
- ``CameraImagePicker``
- ``GalleryImagePicker``

### Alert presentation

- ``AlertModel``
Loading
Loading