Skip to content

App Themes #9

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

Open
wants to merge 8 commits into
base: main
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
13 changes: 13 additions & 0 deletions Packages/DomainModels/Sources/DomainModels/AppTheme.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// File.swift
//
//
// Created by Oscar Gonzalez on 09/03/23.
//

import Foundation

public enum AppTheme: Equatable {
case free
case pro
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Foundation
import SwiftUI
import DomainModels
import Utils
import UIComponents

/// Content is a component of a page. Content accepts bindings or simple primitive types.
public struct CountryDetailsContent: View {
Expand All @@ -34,5 +35,6 @@ public struct CountryDetailsContent: View {
struct CountryDetailsContent_Previews: PreviewProvider {
static var previews: some View {
CountryDetailsContent(countryName: "United States", detailsText: "Now is the time for all good men to come to the aid of their country.")
.theme(ProTheme())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ public struct CountryNotFoundErrorView: View {
struct CountryNotFoundErrorView_Previews: PreviewProvider {
static var previews: some View {
CountryNotFoundErrorView(viewModelState: .error(error: .countryNotFound))
.theme(ProTheme())
}
}
3 changes: 3 additions & 0 deletions Packages/Feature/Sources/Feature/DI/Container+Features.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ import Swinject
import Interfaces
import SwinjectAutoregistration
import Repositories
import UIComponents

public extension Container {
func injectBusinessLogicLocalApis() -> Container {
self.autoregister(AppSchedulerProviding.self, initializer: AppSchedulerProvider.init).inObjectScope(.container)
self.autoregister(Stylesheet.self, initializer: Stylesheet.init).inObjectScope(.container)
self.autoregister(ThemeMediator.self, initializer: ThemeMediator.init).inObjectScope(.container)

return self
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// File.swift
//
//
// Created by Oscar Gonzalez on 13/04/23.
//

import Foundation
import SwiftUI
import Utils
import UIComponents

/// Injects the theme into the view environment
///
/// The use of this modifier is intended for view adapters to avoid
/// using DI on previews
public struct InjectThemeModifier: ViewModifier {
@InjectStateObject var styleSheet: Stylesheet

var theme: ThemeImplementing {
styleSheet.currentTheme
}

public func body(content: Content) -> some View {
content.theme(theme)
}
}

public extension View {
func themeInjected() -> some View {
self.modifier(InjectThemeModifier())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// File.swift
//
//
// Created by Oscar Gonzalez on 10/03/23.
//

import Foundation
import Logic
import UIComponents
import DomainModels
import Utils
import Combine

public class ThemeMediator {

@Inject private var stylesheet: Stylesheet
@Inject private var themeLogic: AppThemeLogic

var cancellables = Set<AnyCancellable>()

public init() {
stylesheet.currentTheme = ThemeFactory.build(theme: themeLogic.currentTheme)

themeLogic
.$currentTheme
.dropFirst()
.map { ThemeFactory.build(theme: $0) }
.assign(to: &stylesheet.$currentTheme)
}
}

public struct ThemeFactory {
public static func build(theme: AppTheme) -> ThemeImplementing {
switch theme {
case .free:
return FreeTheme()
case .pro:
return ProTheme()
}
}
}
29 changes: 29 additions & 0 deletions Packages/Logic/Sources/Logic/AppThemeLogic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// File.swift
//
//
// Created by Oscar Gonzalez on 09/03/23.
//

import Foundation
import DomainModels
import Combine
import Repositories

public class AppThemeLogic {

@Published public private(set) var currentTheme: AppTheme

private let appThemeRepository: AppThemeRepository

public init(appThemeRepository: AppThemeRepository) {
self.appThemeRepository = appThemeRepository

currentTheme = appThemeRepository.currentTheme
appThemeRepository.$currentTheme.dropFirst().assign(to: &$currentTheme)
}

public func changeAppTheme(to theme: AppTheme) {
return appThemeRepository.setTheme(theme)
}
}
2 changes: 2 additions & 0 deletions Packages/Logic/Sources/Logic/DI/Container+BusinessLogic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Repositories

public extension Container {
func injectBusinessLogicLogic() -> Container {
self.autoregister(AppThemeLogic.self, initializer: AppThemeLogic.init).inObjectScope(.transient)
self.autoregister(CountryListLogic.self, initializer: CountryListLogic.init).inObjectScope(.transient)
self.autoregister(CountryDetailsLogic.self, initializer: CountryDetailsLogic.init).inObjectScope(.transient)
self.autoregister(ServerStatusLogic.self, initializer: ServerStatusLogic.init).inObjectScope(.transient)
Expand All @@ -21,6 +22,7 @@ public extension Container {
}

func injectBusinessLogicRepositories() -> Container {
self.autoregister(AppThemeRepository.self, initializer: AppThemeRepository.init).inObjectScope(.container)
self.autoregister(CountryListRepository.self, initializer: CountryListRepository.init).inObjectScope(.container)
self.autoregister(CountryDetailsRepository.self, initializer: CountryDetailsRepository.init).inObjectScope(.container)
self.autoregister(ServerStatusPushBasedRepository.self, initializer: ServerStatusPushBasedRepository.init).inObjectScope(.container)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// File.swift
//
//
// Created by Oscar Gonzalez on 09/03/23.
//

import Foundation
import DomainModels

public class AppThemeRepository {
@Published public private(set) var currentTheme = AppTheme.free

public init() {}

public func setTheme(_ appTheme: AppTheme) {
currentTheme = appTheme
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import SwinjectAutoregistration

public extension Container {
func injectBusinessLogicRepositories() -> Container {
self.autoregister(AppThemeRepository.self, initializer: AppThemeRepository.init).inObjectScope(.container)
self.autoregister(CountryListRepository.self, initializer: CountryListRepository.init).inObjectScope(.container)
self.autoregister(CountryDetailsRepository.self, initializer: CountryDetailsRepository.init).inObjectScope(.container)
self.autoregister(ServerStatusPushBasedRepository.self, initializer: ServerStatusPushBasedRepository.init).inObjectScope(.container)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// SwiftUIView.swift
//
//
// Created by Oscar Gonzalez on 08/03/23.
//

import SwiftUI

struct ColorPalette {

struct malachite {
static let brightness500 = Color(hex: 0x06D16F)
static let brightness700 = Color(hex: 0x00A756)
}

struct bearHoney {
static let brightness500 = Color(hex: 0xE9A142)
static let brightness700 = Color(hex: 0xDA7C00)
}

struct orange {
static let brightness500 = Color(hex: 0xEC7C3C)
static let brightness700 = Color(hex: 0xBB4C0C)
}

static let blastoise = Color(hex: 0x007AFF)
static let charizard = Color(hex: 0xFA0002)
static let latexPurple = Color(hex: 0x7708E7)
static let darkGray = Color(hex: 0x1E1E1E)
static let lightGray = Color(hex: 0xD2D2D2)
static let midgray = Color(hex: 0x4D4D4D)
static let white = Color.white
static let black = Color.black
static let green = Color(hex: 0x00FF00)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// File.swift
//
//
// Created by Oscar Gonzalez on 09/03/23.
//

import Foundation
import SwiftUI
import Utils

public struct ThemeKey: EnvironmentKey {
public static var defaultValue: ThemeImplementing = FreeTheme()
}

public extension EnvironmentValues {
var theme: ThemeImplementing {
get { self[ThemeKey.self] }
set { self[ThemeKey.self] = newValue }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// File.swift
//
//
// Created by Oscar Gonzalez on 24/03/23.
//

import Foundation
import SwiftUI
import Utils

public struct ThemeModifier: ViewModifier {
var theme: ThemeImplementing

public func body(content: Content) -> some View {
content
.accentColor(theme.color.accent)
.foregroundColor(theme.color.content)
.preferredColorScheme(theme.color.scheme)
.environment(\.theme, theme)
}
}

public extension View {
func theme(_ theme: ThemeImplementing) -> some View {
self.modifier(ThemeModifier(theme: theme))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// File.swift
//
//
// Created by Oscar Gonzalez on 08/03/23.
//

import Foundation
import SwiftUI

public struct FreeTheme: ThemeImplementing {
public struct Color: ThemeColor {
public let accent = ColorPalette.blastoise
public let content = ColorPalette.black
public let scheme: ColorScheme = .light
}

public let color: ThemeColor = FreeTheme.Color()

public init() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// File.swift
//
//
// Created by Oscar Gonzalez on 08/03/23.
//

import Foundation
import SwiftUI

public struct ProTheme: ThemeImplementing {
public struct Color: ThemeColor {
public let accent = ColorPalette.charizard
public let content = ColorPalette.white
public let scheme: ColorScheme = .dark
}

public let color: ThemeColor = ProTheme.Color()

public init() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// File.swift
//
//
// Created by Oscar Gonzalez on 10/03/23.
//

import Foundation

public class Stylesheet: ObservableObject {
@Published public var currentTheme: ThemeImplementing = FreeTheme()

public init() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// File.swift
//
//
// Created by Oscar Gonzalez on 08/03/23.
//

import Foundation
import SwiftUI

public protocol ThemeImplementing {
var color: ThemeColor { get }
}

public protocol ThemeColor {
var accent: Color { get }
var content: Color { get }
var scheme: ColorScheme { get }
}
21 changes: 21 additions & 0 deletions Packages/UIComponents/Sources/UIComponents/Utils/Color+Hex.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// File.swift
//
//
// Created by Oscar Gonzalez on 08/03/23.
//

import Foundation
import SwiftUI

extension Color {
init(hex: UInt, alpha: Double = 1) {
self.init(
.sRGB,
red: Double((hex >> 16) & 0xff) / 255,
green: Double((hex >> 08) & 0xff) / 255,
blue: Double((hex >> 00) & 0xff) / 255,
opacity: alpha
)
}
}
Loading