Skip to content

Commit

Permalink
feat: add about module
Browse files Browse the repository at this point in the history
  • Loading branch information
fardavide committed Jan 2, 2024
1 parent cc870ab commit 5f48812
Show file tree
Hide file tree
Showing 15 changed files with 357 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AboutPresentationTests"
BuildableName = "AboutPresentationTests"
BlueprintName = "AboutPresentationTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6BD0FF1A2AF64E7F00CC9579"
BuildableName = "Swiftly.app"
BlueprintName = "Swiftly"
ReferencedContainer = "container:../Swiftly.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6BD0FF1A2AF64E7F00CC9579"
BuildableName = "Swiftly.app"
BlueprintName = "Swiftly"
ReferencedContainer = "container:../Swiftly.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
40 changes: 40 additions & 0 deletions Core/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ let package = Package(
.library(
name: "Core",
targets: [
// MARK: - About declaration
"AboutDomain",
"AboutPresentation",
// MARK: - App Storage declaration
"AppStorage",
"RealAppStorage",
Expand Down Expand Up @@ -48,6 +51,43 @@ let package = Package(
.package(url: "https://github.com/kishikawakatsumi/swift-power-assert", from: Version(0, 12, 0))
],
targets: [

// MARK: - About defintion
// MARK: About Domain
.target(
name: "AboutDomain",
dependencies: [
"Provider"
],
path: "Sources/About/Domain"
),
.testTarget(
name: "AboutDomainTests",
dependencies: [
"AboutDomain"
],
path: "Tests/About/DomainTests"
),

// MARK: About Presentation
.target(
name: "AboutPresentation",
dependencies: [
"AboutDomain",
"Provider",
"SwiftlyUtils"
],
path: "Sources/About/Presentation"
),
.testTarget(
name: "AboutPresentationTests",
dependencies: [
"AboutPresentation",
"SwiftlyTest",
.product(name: "PowerAssert", package: "swift-power-assert")
],
path: "Tests/About/PresentationTests"
),

// MARK: - App Storage definition
// MARK: App Storage
Expand Down
9 changes: 9 additions & 0 deletions Core/Sources/About/Domain/AboutDomainModule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Provider

public final class AboutDomainModule: Module {
public init() {}

public func register(on provider: Provider) {
provider.register { RealGetAppVersion() as GetAppVersion }
}
}
13 changes: 13 additions & 0 deletions Core/Sources/About/Domain/Models/AppVersion.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
public struct AppVersion {
public let major: Int
public let minor: Int

public init(major: Int, minor: Int) {
self.major = major
self.minor = minor
}
}

public enum AppVersionError: Error {
case unknown
}
43 changes: 43 additions & 0 deletions Core/Sources/About/Domain/UseCases/GetAppVersion.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Foundation

public protocol GetAppVersion {

func run() -> Result<AppVersion, AppVersionError>
}

class RealGetAppVersion: GetAppVersion {

func run() -> Result<AppVersion, AppVersionError> {
guard let appVersionString = getBundleVersion() else {
return .failure(.unknown)
}
let parts = appVersionString.split(separator: ".")
return .success(
AppVersion(
major: Int(parts[0])!,
minor: Int(parts[1])!
)
)
}

private func getBundleVersion() -> String? {
Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
}
}

public class FakeGetAppVersion: GetAppVersion {

let appVersionResult: Result<AppVersion, AppVersionError>

public init(appVersionResult: Result<AppVersion, AppVersionError> = .failure(.unknown)) {
self.appVersionResult = appVersionResult
}

public convenience init(appVersion: AppVersion) {
self.init(appVersionResult: .success(appVersion))
}

public func run() -> Result<AppVersion, AppVersionError> {
appVersionResult
}
}
16 changes: 16 additions & 0 deletions Core/Sources/About/Presentation/AboutPresentationModule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import AboutDomain
import Provider

public final class AboutPresentationModule: Module {
public init() {}

public var dependencies: [Module.Type] = [
AboutDomainModule.self
]

public func register(on provider: Provider) {
provider.register {
AboutViewModel(getAppVersion: provider.get())
}
}
}
36 changes: 36 additions & 0 deletions Core/Sources/About/Presentation/AboutViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import AboutDomain
import Foundation
import SwiftlyUtils

public final class AboutViewModel: ViewModel {
public typealias Action = AboutAction
public typealias State = AboutState

private let getAppVersion: GetAppVersion
@Published public var state: AboutState

init(
getAppVersion: GetAppVersion,
initialState: AboutState = AboutState.initial
) {
self.getAppVersion = getAppVersion
state = initialState
Task { load() }
}

public func send(_ action: AboutAction) {
switch action {
case .none: break
}
}

private func load() {
let appVersion: GenericLce<String> = getAppVersion.run()
.map { "\($0.major).\($0.minor)" }
.toLce()

emit {
self.state = AboutState(appVersion: appVersion)
}
}
}
3 changes: 3 additions & 0 deletions Core/Sources/About/Presentation/Models/AboutAction.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
public enum AboutAction {
case none
}
14 changes: 14 additions & 0 deletions Core/Sources/About/Presentation/Models/AboutState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Foundation
import SwiftlyUtils

public struct AboutState {
let appVersion: GenericLce<String>
}

extension AboutState {
static var initial: AboutState {
AboutState(
appVersion: .loading
)
}
}
18 changes: 18 additions & 0 deletions Core/Sources/About/Presentation/UI/AboutView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Provider
import SwiftUI

public struct AboutView: View {
@StateObject var viewModel: AboutViewModel = getProvider().get()

public init() {}

public var body: some View {
VStack {

}
}
}

#Preview {
AboutView()
}
1 change: 1 addition & 0 deletions Core/Sources/Common/Utils/GenericError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
public struct GenericError: Error, Equatable {}
32 changes: 32 additions & 0 deletions Core/Sources/Common/Utils/Lce.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/// Loading, Content, Error construct
public enum Lce<C, E: Error>: Equatable where C: Equatable, E: Equatable {
case content(C)
case error(E)
case loading
}

/// Lce with GenericError
public typealias GenericLce<C: Equatable> = Lce<C, GenericError>

public extension Result where Success: Equatable {

/// Map Result to Lce
/// - Parameter error: closure that maps a Result's Failure to Lce's Error
func toLce<E>(error: (Failure) -> E) -> Lce<Success, E> where E: Error, E: Equatable {
switch self {
case let .failure(e): Lce.error(error(e))
case let .success(content): Lce.content(content)
}
}

/// Maps Result to GenericLce
func toLce() -> GenericLce<Success> {
toLce(error: { _ in GenericError() })
}
}

public extension Lce where E == GenericError {
static var error: Lce {
.error(GenericError())
}
}
8 changes: 8 additions & 0 deletions Core/Tests/About/DomainTests/AboutDomainTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//
// File.swift
//
//
// Created by Davide Giuseppe Farella on 02/01/24.
//

import Foundation
50 changes: 50 additions & 0 deletions Core/Tests/About/PresentationTests/AboutViewModelTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import XCTest

import AboutDomain
import PowerAssert
import SwiftlyTest
@testable import AboutPresentation

final class AboutViewModelTests: XCTestCase {

func test_initialAppVersionIsLoading() {
// given
let scenario = Scenario()

// then
#assert(scenario.sut.state.appVersion == .loading)
}

func test_appVersionIsLoadedCorrectly() async {
// given
let scenario = Scenario(appVersion: AppVersion(major: 1, minor: 2))

// when
await test(scenario.sut.$state.map(\.appVersion)) { turbine in
await turbine.expectInitial(value: .loading)

// then
let result = await turbine.value()
#assert(result == .content("1.2"))
}
}
}

private class Scenario {

let sut: AboutViewModel

init(
getAppVersion: GetAppVersion = FakeGetAppVersion()
) {
sut = AboutViewModel(getAppVersion: getAppVersion)
}

convenience init(
appVersion: AppVersion
) {
self.init(
getAppVersion: FakeGetAppVersion(appVersion: appVersion)
)
}
}
2 changes: 2 additions & 0 deletions Swiftly/SwiftlyModule.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import AboutPresentation
import DateUtils
import Provider
import ConverterData
Expand All @@ -8,6 +9,7 @@ import RealAppStorage
final class SwiftlyModule: Module {

var dependencies: [Module.Type] = [
AboutPresentationModule.self,
AppStorageModule.self,
ConverterDataModule.self,
ConverterPresentionModule.self,
Expand Down

0 comments on commit 5f48812

Please sign in to comment.