From a5a41155ae21c5f3e274f5f137fb39c937915b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Sat, 30 Sep 2023 19:29:12 +0900 Subject: [PATCH 01/19] =?UTF-8?q?feat:=20localized=20string=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Target+Module.swift | 4 +- .../App/Resources/Localizable.xcstrings | 142 ++++++++++++ .../Interface/Sources/Model/TradeSide.swift | 4 +- .../Sources/Main/PortfolioMainView.swift | 1 + .../Components/Cell/TradeItemCellView.swift | 2 +- .../Util/Resources/Localizable.xcstrings | 210 ++++++++++++++++++ 6 files changed, 358 insertions(+), 5 deletions(-) create mode 100644 Projects/Toolinder/App/Resources/Localizable.xcstrings create mode 100644 Projects/Toolinder/Shared/Util/Resources/Localizable.xcstrings diff --git a/Plugins/ModulePlugin/ProjectDescriptionHelpers/Target+Module.swift b/Plugins/ModulePlugin/ProjectDescriptionHelpers/Target+Module.swift index 16eaf13..3858a8f 100644 --- a/Plugins/ModulePlugin/ProjectDescriptionHelpers/Target+Module.swift +++ b/Plugins/ModulePlugin/ProjectDescriptionHelpers/Target+Module.swift @@ -309,7 +309,7 @@ public extension Target { deploymentTarget: .shared(product), infoPlist: .shared(product), sources: .path(type: .implement), - resources: nil, + resources: .path(type: .implement), copyFiles: nil, headers: nil, entitlements: nil, @@ -341,7 +341,7 @@ public extension Target { deploymentTarget: .shared(product, module: module), infoPlist: .shared(product, module: module), sources: .path(type: type), - resources: nil, + resources: .path(type: .implement), copyFiles: nil, headers: nil, entitlements: nil, diff --git a/Projects/Toolinder/App/Resources/Localizable.xcstrings b/Projects/Toolinder/App/Resources/Localizable.xcstrings new file mode 100644 index 0000000..62ecc38 --- /dev/null +++ b/Projects/Toolinder/App/Resources/Localizable.xcstrings @@ -0,0 +1,142 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "Buy" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "売り" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "매수" + } + } + } + }, + "Edit" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "編集" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "편집" + } + } + } + }, + "History" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "記録" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "기록" + } + } + } + }, + "Save" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "保存" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "저장" + } + } + } + }, + "Sell" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "買い" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "매도" + } + } + } + }, + "Summary" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "概略" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "요약" + } + } + } + }, + "Ticker" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "銘柄" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "종목" + } + } + } + }, + "Trade" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "商売" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "거래" + } + } + } + } + }, + "version" : "1.0" +} \ No newline at end of file diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/Model/TradeSide.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/Model/TradeSide.swift index d159c4c..dd4a1e7 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/Model/TradeSide.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/Model/TradeSide.swift @@ -8,6 +8,6 @@ import Foundation public enum TradeSide: String, Codable, CaseIterable { - case buy - case sell + case buy = "Buy" + case sell = "Sell" } diff --git a/Projects/Toolinder/Feature/Portfolio/Interface/Sources/Main/PortfolioMainView.swift b/Projects/Toolinder/Feature/Portfolio/Interface/Sources/Main/PortfolioMainView.swift index fb5afa0..6c0631a 100644 --- a/Projects/Toolinder/Feature/Portfolio/Interface/Sources/Main/PortfolioMainView.swift +++ b/Projects/Toolinder/Feature/Portfolio/Interface/Sources/Main/PortfolioMainView.swift @@ -11,6 +11,7 @@ import Charts import ComposableArchitecture import ToolinderFeatureTradeInterface +import ToolinderShared public struct PortfolioMainView: View { public let store: StoreOf diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TradeItemCellView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TradeItemCellView.swift index f20f91f..1fa4894 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TradeItemCellView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TradeItemCellView.swift @@ -58,7 +58,7 @@ public struct TradeItemCellView: View { .font(.caption2) } } - .frame(width: 70) + .frame(maxWidth: 100) viewStore.state.trade.ticker?.type.image .font(.title3) diff --git a/Projects/Toolinder/Shared/Util/Resources/Localizable.xcstrings b/Projects/Toolinder/Shared/Util/Resources/Localizable.xcstrings new file mode 100644 index 0000000..e940975 --- /dev/null +++ b/Projects/Toolinder/Shared/Util/Resources/Localizable.xcstrings @@ -0,0 +1,210 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "Buy" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "売り" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "매수" + } + } + } + }, + "Currency" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "通貨" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "화폐" + } + } + } + }, + "Edit" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "編集" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "편집" + } + } + } + }, + "History" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "記録" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "기록" + } + } + } + }, + "Name" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "名" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이름" + } + } + } + }, + "Next" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "次の" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "다음" + } + } + } + }, + "Save" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "保存" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "저장" + } + } + } + }, + "Sell" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "買い" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "매도" + } + } + } + }, + "Summary" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "概略" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "요약" + } + } + } + }, + "Ticker" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "銘柄" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "종목" + } + } + } + }, + "Ticker Type" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "銘柄 種類" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "종목 종류" + } + } + } + }, + "Trade" : { + "extractionState" : "manual", + "localizations" : { + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "商売" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "거래" + } + } + } + } + }, + "version" : "1.0" +} \ No newline at end of file From 69053b3997f293e3d362cfd7a2fd30f6a4635fbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Sat, 30 Sep 2023 19:50:17 +0900 Subject: [PATCH 02/19] =?UTF-8?q?feat:=20tag=20=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/PersistentModel/Tag.swift | 26 +++++++++++++++++++ .../Sources/PersistentModel/Ticker.swift | 1 + 2 files changed, 27 insertions(+) create mode 100644 Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Tag.swift diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Tag.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Tag.swift new file mode 100644 index 0000000..5bdef30 --- /dev/null +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Tag.swift @@ -0,0 +1,26 @@ +// +// HashTag.swift +// ToolinderDomainTradeInterface +// +// Created by 송영모 on 2023/09/30. +// + +import Foundation +import SwiftData +import SwiftUI + +@Model +public class Tag { + public var color: Color = Color.blue + public var name: String = "" + + @Relationship public var tickers: [Ticker] = [] + + public init( + color: Color, + name: String, + tickers: [Ticker] + ) { + self.tickers = tickers + } +} diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Ticker.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Ticker.swift index 5475a9b..1558ac1 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Ticker.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Ticker.swift @@ -15,6 +15,7 @@ public class Ticker { public var name: String = "" @Relationship(deleteRule: .cascade, inverse: \Trade.ticker) public var trades: [Trade]? = [] + @Relationship(inverse: \Tag.tickers) public var tags: [Tag]? = [] public init( type: TickerType, From a0e129ba01bdb7be1d94bc9fcfdf94a1a657885f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Sun, 1 Oct 2023 19:14:06 +0900 Subject: [PATCH 03/19] =?UTF-8?q?feat:=20tag=20logic=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Trade/Interface/Sources/DTO/TagDTO.swift | 34 ++++++ .../Interface/Sources/DTO/TickerDTO.swift | 8 +- .../Interface/Sources/DTO/TradeDTO.swift | 4 + .../Sources/PersistentModel/Tag.swift | 9 +- .../Sources/PersistentModel/Ticker.swift | 7 +- .../Sources/PersistentModel/Trade.swift | 3 + .../Sources/Repository/TagRepository.swift | 76 +++++++++++++ .../Sources/Repository/TradeRepository.swift | 105 ++++-------------- .../Trade/Interface/Sources/TagClient.swift | 68 ++++++++++++ .../Sources/SelectTag/SelectTagStore.swift | 47 ++++++++ .../Sources/SelectTag/SelectTagView.swift | 54 +++++++++ 11 files changed, 329 insertions(+), 86 deletions(-) create mode 100644 Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TagDTO.swift create mode 100644 Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TagRepository.swift create mode 100644 Projects/Toolinder/Domain/Trade/Interface/Sources/TagClient.swift create mode 100644 Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagStore.swift create mode 100644 Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagView.swift diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TagDTO.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TagDTO.swift new file mode 100644 index 0000000..6ec09e1 --- /dev/null +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TagDTO.swift @@ -0,0 +1,34 @@ +// +// TagDTO.swift +// ToolinderDomainTradeInterface +// +// Created by 송영모 on 2023/10/01. +// + +import Foundation +import SwiftData +import SwiftUI + +public class TagDTO { + public let id: UUID = UUID() + public var color: Color = Color.blue + public var name: String = "" + + public init( + id: UUID = .init(), + color: Color, + name: String + ) { + self.id = id + self.color = color + self.name = name + } + + func toDomain() -> Tag { + return Tag( + id: id, + color: color, + name: name + ) + } +} diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TickerDTO.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TickerDTO.swift index 96a995b..87bb83e 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TickerDTO.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TickerDTO.swift @@ -9,15 +9,19 @@ import Foundation import SwiftData public class TickerDTO { + public let id: UUID public var type: TickerType public var currency: Currency public var name: String + public var tag: Tag public init( + id: UUID = .init(), type: TickerType, currency: Currency, name: String ) { + self.id = id self.type = type self.currency = currency self.name = name @@ -25,9 +29,11 @@ public class TickerDTO { func toDomain() -> Ticker { return Ticker( + id: id, type: type, currency: currency, - name: name + name: name, + tags: ) } } diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TradeDTO.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TradeDTO.swift index 8ea8e65..22bb0a5 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TradeDTO.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TradeDTO.swift @@ -9,6 +9,7 @@ import Foundation import SwiftData public struct TradeDTO { + public let id: UUID public var side: TradeSide public var price: Double public var volume: Double @@ -20,6 +21,7 @@ public struct TradeDTO { public var ticker: Ticker? public init( + id: UUID = .init(), side: TradeSide = .buy, price: Double = 0, volume: Double = 0, @@ -29,6 +31,7 @@ public struct TradeDTO { date: Date = .now, ticker: Ticker ) { + self.id = id self.side = side self.images = images self.price = price @@ -41,6 +44,7 @@ public struct TradeDTO { func toDomain() -> Trade { return Trade( + id: id, side: side, price: price, volume: volume, diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Tag.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Tag.swift index 5bdef30..923cf4b 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Tag.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Tag.swift @@ -11,16 +11,19 @@ import SwiftUI @Model public class Tag { + public let id: UUID = UUID() public var color: Color = Color.blue public var name: String = "" @Relationship public var tickers: [Ticker] = [] public init( + id: UUID = .init(), color: Color, - name: String, - tickers: [Ticker] + name: String ) { - self.tickers = tickers + self.id = id + self.color = color + self.name = name } } diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Ticker.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Ticker.swift index 1558ac1..23f2897 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Ticker.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Ticker.swift @@ -10,6 +10,7 @@ import SwiftData @Model public class Ticker { + public let id: UUID = UUID() public var type: TickerType = TickerType.stock public var currency: Currency = Currency.dollar public var name: String = "" @@ -18,12 +19,16 @@ public class Ticker { @Relationship(inverse: \Tag.tickers) public var tags: [Tag]? = [] public init( + id: UUID = .init(), type: TickerType, currency: Currency, - name: String + name: String, + tags: [Tag] ) { + self.id = id self.type = type self.currency = currency self.name = name + self.tags = tags } } diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Trade.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Trade.swift index e91b9aa..4098e56 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Trade.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Trade.swift @@ -10,6 +10,7 @@ import SwiftData @Model public class Trade { + public let id: UUID = UUID() public var side: TradeSide = TradeSide.buy public var price: Double = 0 public var volume: Double = 0 @@ -21,6 +22,7 @@ public class Trade { @Relationship public var ticker: Ticker? public init( + id: UUID = .init(), side: TradeSide, price: Double, volume: Double, @@ -30,6 +32,7 @@ public class Trade { date: Date, ticker: Ticker? ) { + self.id = id self.side = side self.images = images self.price = price diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TagRepository.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TagRepository.swift new file mode 100644 index 0000000..a4f893f --- /dev/null +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TagRepository.swift @@ -0,0 +1,76 @@ +// +// TagRepository.swift +// ToolinderDomainTradeInterface +// +// Created by 송영모 on 2023/10/01. +// + +import Foundation +import SwiftData + +public protocol TagRepositoryInterface { + func fetchTags(descriptor: FetchDescriptor) -> Result<[Tag], TagError> + func saveTag(_ tag: TagDTO) -> Result + func updateTag(_ tag: Tag, new newTag: TagDTO) -> Result + func deleteTag(_ tag: Tag) -> Result + + func isValidatedSaveTag(_ tag: TagDTO) -> Bool + func isValidatedUpdateTag(_ tag: Tag, new newTag: TagDTO) -> Bool + func isValidatedDeleteTag(_ tag: Tag) -> Bool +} + +public class TagRepository: TagRepositoryInterface { + private var context: ModelContext? = StorageContainer.shared.context + + public init() { } + + public func fetchTags(descriptor: FetchDescriptor) -> Result<[Tag], TagError> { + if let tags = try? context?.fetch(descriptor) { + return .success(tags) + } else { + return .failure(.unknown) + } + } + + public func saveTag(_ tag: TagDTO) -> Result { + if isValidatedSaveTag(tag) { + let tag = tag.toDomain() + context?.insert(tag) + return .success(tag) + } else { + return .failure(.unknown) + } + } + + public func updateTag(_ tag: Tag, new newTag: TagDTO) -> Result { + if isValidatedUpdateTrade(tag, new: newTag) { + let tag = tag + tag.color = newTag.color + tag.name = newTag.name + return .success(tag) + } else { + return .failure(.unknown) + } + } + + public func deleteTag(_ tag: Tag) -> Result { + if isValidatedDeleteTag(tag) { + context?.delete(tag) + return .success(tag) + } else { + return .failure(.unknown) + } + } + + public func isValidatedSaveTag(_ tag: TagDTO) -> Bool { + return true + } + + public func isValidatedUpdateTag(_ tag: Tag, new newTag: TagDTO) -> Bool { + return true + } + + public func isValidatedDeleteTag(_ tag: Tag) -> Bool { + return true + } +} diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TradeRepository.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TradeRepository.swift index 002d086..555d3ae 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TradeRepository.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TradeRepository.swift @@ -10,13 +10,13 @@ import SwiftData public protocol TradeRepositoryInterface { func fetchTrades(descriptor: FetchDescriptor) -> Result<[Trade], TradeError> - func saveTrade(dto: TradeDTO) -> Result - func updateTrade(model: Trade, dto: TradeDTO) -> Result - func deleteTrade(trade: Trade) -> Result + func saveTrade(_ trade: TradeDTO) -> Result + func updateTrade(_ trade: Trade, new newTrade: TradeDTO) -> Result + func deleteTrade(_ trade: Trade) -> Result - func isValidatedSaveTrade(dto: TradeDTO) -> Bool - func isValidatedUpdateTrade(origin: Trade, new: TradeDTO) -> Bool - func isValidatedDeleteTrade(origin: Trade) -> Bool + func isValidatedSaveTrade(_ trade: TradeDTO) -> Bool + func isValidatedUpdateTrade(_ trade: Trade, new newTrade: TradeDTO) -> Bool + func isValidatedDeleteTrade(_ trade: Trade) -> Bool } public class TradeRepository: TradeRepositoryInterface { @@ -24,44 +24,6 @@ public class TradeRepository: TradeRepositoryInterface { public init() { } - public func fetchTickers(descriptor: FetchDescriptor) -> Result<[Ticker], TradeError> { - if let tickers = try? context?.fetch(descriptor) { - return .success(tickers) - } else { - return .failure(.unknown) - } - } - - public func saveTicker(ticker: Ticker) -> Result { - if isValidatedSaveTicker(new: ticker) { - context?.insert(ticker) - return .success(ticker) - } else { - return .failure(.unknown) - } - } - - public func updateTicker(model: Ticker, dto: TickerDTO) -> Result { - if isValidatedUpdateTicker(origin: model, new: dto) { - let ticker = model - ticker.type = dto.type - ticker.currency = dto.currency - ticker.name = dto.name - return .success(ticker) - } else { - return .failure(.unknown) - } - } - - public func deleteTicker(ticker: Ticker) -> Result { - if isValidatedDeleteTicker(origin: ticker) { - context?.delete(ticker) - return .success(ticker) - } else { - return .failure(.unknown) - } - } - public func fetchTrades(descriptor: FetchDescriptor) -> Result<[Trade], TradeError> { if let trades = try? context?.fetch(descriptor) { return .success(trades) @@ -70,8 +32,8 @@ public class TradeRepository: TradeRepositoryInterface { } } - public func saveTrade(dto: TradeDTO) -> Result { - if isValidatedSaveTrade(dto: dto) { + public func saveTrade(_ trade: TradeDTO) -> Result { + if isValidatedSaveTrade(trade) { let trade = dto.toDomain() context?.insert(trade) return .success(trade) @@ -105,79 +67,60 @@ public class TradeRepository: TradeRepositoryInterface { } } - public func isValidatedSaveTicker(new: Ticker) -> Bool { - return true - } - - public func isValidatedUpdateTicker(origin: Ticker, new: TickerDTO) -> Bool { - if new.type != nil && new.currency != nil && new.name.isEmpty == false { - return true - } - return false - } - - public func isValidatedDeleteTicker(origin: Ticker) -> Bool { - return true - } - public func isValidatedSaveTrade(dto: TradeDTO) -> Bool { if dto.side == .buy { return true } - - guard let trades = try? fetchTrades(descriptor: .init(sortBy: [.init(\.date)])).get().filter({ $0.ticker == dto.ticker }) + guard let trades = try? fetchTrades(descriptor: .init()).get().filter({ $0.ticker == dto.ticker }).sorted(by: { $0.date < $1.date }) else { return false } let currentVolume = trades.reduce(0) { (result, trade) in if trade.side == .buy { - return result + (trade.volume ?? 0) + return result + trade.volume } else { - return result - (trade.volume ?? 0) + return result - trade.volume } } - return currentVolume - (dto.volume ?? 0) > 0 + return currentVolume - dto.volume > 0 } - public func isValidatedUpdateTrade(origin: Trade, new: TradeDTO) -> Bool { - if origin.side == .sell && new.side == .buy { return true } - - guard let trades = try? fetchTrades(descriptor: .init(sortBy: [.init(\.date)])).get().filter({ $0.ticker == origin.ticker }) + public func isValidatedUpdateTrade(_ trade: Trade, new newTrade: TradeDTO) -> Bool { + if trade.side == .sell && newTrade.side == .buy { return true } + guard let trades = try? fetchTrades(descriptor: .init()).get().filter({ $0.ticker == trade.ticker }).sorted(by: { $0.date < $1.date }) else { return false } var currentVolume = 0.0 - for trade in trades { + for tmpTrade in trades { if trade.side == .buy { - currentVolume += trade == origin ? (new.volume ?? 0) : (trade.volume ?? 0) + currentVolume += tmpTrade == trade ? newTrade.volume : tmpTrade.volume } else { - currentVolume -= trade == origin ? (new.volume ?? 0) : (trade.volume ?? 0) + currentVolume -= tmpTrade == trade ? newTrade.volume : tmpTrade.volume } if currentVolume < 0 { return false } } - return true } - public func isValidatedDeleteTrade(origin: Trade) -> Bool { - if origin.side == .sell { return true } + public func isValidatedDeleteTrade(_ trade: Trade) -> Bool { + if trade.side == .sell { return true } - guard let trades = try? fetchTrades(descriptor: .init(sortBy: [.init(\.date)])).get().filter({ $0.ticker == origin.ticker }) + guard let trades = try? fetchTrades(descriptor: .init(sortBy: [.init(\.date)])).get().filter({ $0.ticker == trade.ticker }) else { return false } var currentVolume = 0.0 - for trade in trades { + for tmpTrade in trades { if trade.side == .buy { - currentVolume += trade == origin ? 0 : (trade.volume ?? 0) + currentVolume += tmpTrade == trade ? 0 : tmpTrade.volume } else { - currentVolume -= trade == origin ? 0 : (trade.volume ?? 0) + currentVolume -= tmpTrade == trade ? 0 : tmpTrade.volume } if currentVolume < 0 { return false } } - return true } } diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/TagClient.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/TagClient.swift new file mode 100644 index 0000000..7dc6729 --- /dev/null +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/TagClient.swift @@ -0,0 +1,68 @@ +// +// TagClient.swift +// ToolinderDomainTradeInterface +// +// Created by 송영모 on 2023/10/01. +// + +import Foundation +import SwiftData + +import ComposableArchitecture + +public enum TagError: Error { + case unknown +} + +public struct TagClient { + public static let tagRepository: TagRepositoryInterface = TagRepository() + + public var fetchTags: () -> Result<[Tag], TagError> + public var saveTag: (TagDTO) -> Result + public var updateTag: (Tag, TagDTO) -> Result + public var deleteTag: (Tag) -> Result + + public init( + fetchTags: @escaping () -> Result<[Tag], TagError>, + saveTag: @escaping (TagDTO) -> Result, + updateTag: @escaping (Tag, TagDTO) -> Result, + deleteTag: @escaping (Tag) -> Result + ) { + self.fetchTags = fetchTags + self.saveTag = saveTag + self.updateTag = updateTag + self.deleteTag = deleteTag + } +} + +extension TagClient: TestDependencyKey { + public static var previewValue: TagClient = Self( + fetchTags: { return .failure(.unknown) }, + saveTag: { _ in return .failure(.unknown) }, + updateTag: { _, _ in return .failure(.unknown) }, + deleteTag: { _ in return .failure(.unknown) } + ) + + public static var testValue = Self( + fetchTags: unimplemented("\(Self.self).fetchTags"), + saveTag: unimplemented("\(Self.self).saveTag"), + updateTag: unimplemented("\(Self.self).updateTag"), + deleteTag: unimplemented("\(Self.self).deleteTag") + ) +} + +public extension DependencyValues { + var tagClient: TagClient { + get { self[TagClient.self] } + set { self[TagClient.self] = newValue } + } +} + +extension TagClient: DependencyKey { + public static var liveValue = TagClient( + fetchTags: { tagRepository.fetchTags(descriptor: .init()) }, + saveTag: { tagRepository.saveTag($0) }, + updateTag: { tagRepository.updateTag($0, new: $1) }, + deleteTag: { tagRepository.deleteTag($0) } + ) +} diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagStore.swift new file mode 100644 index 0000000..cdf97ac --- /dev/null +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagStore.swift @@ -0,0 +1,47 @@ +// +// SelectTagStore.swift +// ToolinderFeatureTradeInterface +// +// Created by 송영모 on 2023/10/01. +// + +import Foundation + +import ComposableArchitecture + +import ToolinderDomain + +public struct SelectTagStore: Reducer { + public init() {} + + public struct State: Equatable { + public init() { } + } + + public enum Action: Equatable { + case onAppear + + case currencyTapped(Currency) + + case delegate(Delegate) + + public enum Delegate: Equatable { + case select(Currency) + } + } + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case .onAppear: + return .none + + case let .currencyTapped(currency): + return .send(.delegate(.select(currency))) + + default: + return .none + } + } + } +} diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagView.swift new file mode 100644 index 0000000..d6d9aae --- /dev/null +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagView.swift @@ -0,0 +1,54 @@ +// +// SelectTagView.swift +// ToolinderFeatureTradeInterface +// +// Created by 송영모 on 2023/10/01. +// + +import Foundation +import SwiftUI + +import ComposableArchitecture + +import ToolinderDomain +import ToolinderShared + +public struct SelectTagView: View { + let store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + WithViewStore(self.store, observe: { $0 }) { viewStore in + ScrollView { + HStack { + Text("Currency") + .font(.title) + Spacer() + } + .padding() + + LazyVGrid(columns: .init(repeating: .init(.flexible(minimum: 10, maximum: 500)), count: 3), alignment: .leading, spacing: 10) { + ForEach(Currency.allCases, id: \.self) { currency in + Label(currency.rawValue, systemImage: currency.systemImageName) + .font(.body) + .padding(10) + .background(Color(uiColor: .systemGray6)) + .clipShape( + RoundedRectangle( + cornerRadius: 8, + style: .continuous + ) + ) + .onTapGesture { + viewStore.send(.delegate(.select(currency))) + } + } + } + .padding(.horizontal) + } + } + } +} From 7a1295e0f1d720a67a20c84cb6e6f92d10dfc57a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Sun, 1 Oct 2023 19:38:49 +0900 Subject: [PATCH 04/19] =?UTF-8?q?feat:=20tag=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Toolinder/App/Sources/RootApp.swift | 14 ++++---- .../Trade/Interface/Sources/DTO/TagDTO.swift | 10 +++--- .../Interface/Sources/DTO/TickerDTO.swift | 8 +++-- .../Sources/PersistentModel/Tag.swift | 8 ++--- .../Sources/Repository/StorageContainer.swift | 3 +- .../Sources/Repository/TagRepository.swift | 4 +-- .../Sources/Repository/TradeRepository.swift | 34 +++++++++---------- .../Trade/Interface/Sources/TagClient.swift | 32 ++++++++++++++--- .../Trade/Interface/Sources/TradeClient.swift | 6 ++-- .../Sources/EditTicker/TickerEditStore.swift | 4 +-- 10 files changed, 74 insertions(+), 49 deletions(-) diff --git a/Projects/Toolinder/App/Sources/RootApp.swift b/Projects/Toolinder/App/Sources/RootApp.swift index 169eb73..46fde9d 100644 --- a/Projects/Toolinder/App/Sources/RootApp.swift +++ b/Projects/Toolinder/App/Sources/RootApp.swift @@ -15,14 +15,14 @@ import ToolinderDomain @main struct RootApp: App { - let modelContainer: ModelContainer +// let modelContainer: ModelContainer init() { - do { - modelContainer = try ModelContainer(for: Ticker.self, Trade.self) - } catch { - fatalError("Could not initialize ModelContainer \(error)") - } +// do { +// modelContainer = try ModelContainer(for: Ticker.self, Trade.self, Tag.self) +// } catch { +// fatalError("Could not initialize ModelContainer \(error)") +// } } var body: some Scene { WindowGroup { @@ -34,6 +34,6 @@ struct RootApp: App { ) .onAppear(perform: UIApplication.shared.hideKeyboard) } - .modelContainer(modelContainer) +// .modelContainer(modelContainer) } } diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TagDTO.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TagDTO.swift index 6ec09e1..de18175 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TagDTO.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TagDTO.swift @@ -10,24 +10,24 @@ import SwiftData import SwiftUI public class TagDTO { - public let id: UUID = UUID() - public var color: Color = Color.blue + public var id: UUID = UUID() + public var hex: String = "" public var name: String = "" public init( id: UUID = .init(), - color: Color, + hex: String, name: String ) { self.id = id - self.color = color + self.hex = hex self.name = name } func toDomain() -> Tag { return Tag( id: id, - color: color, + hex: hex, name: name ) } diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TickerDTO.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TickerDTO.swift index 87bb83e..63f211b 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TickerDTO.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TickerDTO.swift @@ -13,18 +13,20 @@ public class TickerDTO { public var type: TickerType public var currency: Currency public var name: String - public var tag: Tag + public var tags: [TagDTO] public init( id: UUID = .init(), type: TickerType, currency: Currency, - name: String + name: String, + tags: [TagDTO] ) { self.id = id self.type = type self.currency = currency self.name = name + self.tags = tags } func toDomain() -> Ticker { @@ -33,7 +35,7 @@ public class TickerDTO { type: type, currency: currency, name: name, - tags: + tags: tags.map { $0.toDomain() } ) } } diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Tag.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Tag.swift index 923cf4b..eb98edb 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Tag.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Tag.swift @@ -12,18 +12,18 @@ import SwiftUI @Model public class Tag { public let id: UUID = UUID() - public var color: Color = Color.blue + public var hex: String = "" public var name: String = "" - @Relationship public var tickers: [Ticker] = [] + @Relationship public var tickers: [Ticker]? = [] public init( id: UUID = .init(), - color: Color, + hex: String, name: String ) { self.id = id - self.color = color + self.hex = hex self.name = name } } diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/StorageContainer.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/StorageContainer.swift index a2706db..812ff4e 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/StorageContainer.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/StorageContainer.swift @@ -16,8 +16,7 @@ public class StorageContainer { private init() { do { - container = try ModelContainer(for: Ticker.self, Trade.self) - + container = try ModelContainer(for: Ticker.self, Trade.self, Tag.self) if let container { context = ModelContext(container) } diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TagRepository.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TagRepository.swift index a4f893f..5530a0d 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TagRepository.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TagRepository.swift @@ -43,9 +43,9 @@ public class TagRepository: TagRepositoryInterface { } public func updateTag(_ tag: Tag, new newTag: TagDTO) -> Result { - if isValidatedUpdateTrade(tag, new: newTag) { + if isValidatedUpdateTag(tag, new: newTag) { let tag = tag - tag.color = newTag.color + tag.hex = newTag.hex tag.name = newTag.name return .success(tag) } else { diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TradeRepository.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TradeRepository.swift index 555d3ae..bf53051 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TradeRepository.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TradeRepository.swift @@ -34,7 +34,7 @@ public class TradeRepository: TradeRepositoryInterface { public func saveTrade(_ trade: TradeDTO) -> Result { if isValidatedSaveTrade(trade) { - let trade = dto.toDomain() + let trade = trade.toDomain() context?.insert(trade) return .success(trade) } else { @@ -42,24 +42,24 @@ public class TradeRepository: TradeRepositoryInterface { } } - public func updateTrade(model: Trade, dto: TradeDTO) -> Result { - if isValidatedUpdateTrade(origin: model, new: dto) { - let trade = model - trade.ticker = dto.ticker - trade.date = dto.date - trade.side = dto.side - trade.price = dto.price - trade.volume = dto.volume - trade.images = dto.images - trade.note = dto.note + public func updateTrade(_ trade: Trade, new newTrade: TradeDTO) -> Result { + if isValidatedUpdateTrade(trade, new: newTrade) { + let trade = trade + trade.ticker = newTrade.ticker + trade.date = newTrade.date + trade.side = newTrade.side + trade.price = newTrade.price + trade.volume = newTrade.volume + trade.images = newTrade.images + trade.note = newTrade.note return .success(trade) } else { return .failure(.unknown) } } - public func deleteTrade(trade: Trade) -> Result { - if isValidatedDeleteTrade(origin: trade) { + public func deleteTrade(_ trade: Trade) -> Result { + if isValidatedDeleteTrade(trade) { context?.delete(trade) return .success(trade) } else { @@ -67,9 +67,9 @@ public class TradeRepository: TradeRepositoryInterface { } } - public func isValidatedSaveTrade(dto: TradeDTO) -> Bool { - if dto.side == .buy { return true } - guard let trades = try? fetchTrades(descriptor: .init()).get().filter({ $0.ticker == dto.ticker }).sorted(by: { $0.date < $1.date }) + public func isValidatedSaveTrade(_ trade: TradeDTO) -> Bool { + if trade.side == .buy { return true } + guard let trades = try? fetchTrades(descriptor: .init()).get().filter({ $0.ticker == trade.ticker }).sorted(by: { $0.date < $1.date }) else { return false } let currentVolume = trades.reduce(0) { (result, trade) in @@ -80,7 +80,7 @@ public class TradeRepository: TradeRepositoryInterface { } } - return currentVolume - dto.volume > 0 + return currentVolume - trade.volume >= 0 } public func isValidatedUpdateTrade(_ trade: Trade, new newTrade: TradeDTO) -> Bool { diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/TagClient.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/TagClient.swift index 7dc6729..d2d7e03 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/TagClient.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/TagClient.swift @@ -21,17 +21,29 @@ public struct TagClient { public var saveTag: (TagDTO) -> Result public var updateTag: (Tag, TagDTO) -> Result public var deleteTag: (Tag) -> Result + + public var isValidatedSaveTag: (TagDTO) -> Bool + public var isValidatedUpdateTag: (Tag, TagDTO) -> Bool + public var isValidatedDeleteTag: (Tag) -> Bool public init( fetchTags: @escaping () -> Result<[Tag], TagError>, saveTag: @escaping (TagDTO) -> Result, updateTag: @escaping (Tag, TagDTO) -> Result, - deleteTag: @escaping (Tag) -> Result + deleteTag: @escaping (Tag) -> Result, + + isValidatedSaveTag: @escaping (TagDTO) -> Bool, + isValidatedUpdateTag: @escaping (Tag, TagDTO) -> Bool, + isValidatedDeleteTag: @escaping (Tag) -> Bool ) { self.fetchTags = fetchTags self.saveTag = saveTag self.updateTag = updateTag self.deleteTag = deleteTag + + self.isValidatedSaveTag = isValidatedSaveTag + self.isValidatedUpdateTag = isValidatedUpdateTag + self.isValidatedDeleteTag = isValidatedDeleteTag } } @@ -40,14 +52,22 @@ extension TagClient: TestDependencyKey { fetchTags: { return .failure(.unknown) }, saveTag: { _ in return .failure(.unknown) }, updateTag: { _, _ in return .failure(.unknown) }, - deleteTag: { _ in return .failure(.unknown) } + deleteTag: { _ in return .failure(.unknown) }, + + isValidatedSaveTag: { _ in return true }, + isValidatedUpdateTag: { _, _ in return true }, + isValidatedDeleteTag: { _ in return true } ) public static var testValue = Self( fetchTags: unimplemented("\(Self.self).fetchTags"), saveTag: unimplemented("\(Self.self).saveTag"), updateTag: unimplemented("\(Self.self).updateTag"), - deleteTag: unimplemented("\(Self.self).deleteTag") + deleteTag: unimplemented("\(Self.self).deleteTag"), + + isValidatedSaveTag: unimplemented("\(Self.self).isValidatedSaveTag"), + isValidatedUpdateTag: unimplemented("\(Self.self).isValidatedUpdateTag"), + isValidatedDeleteTag: unimplemented("\(Self.self).isValidatedDeleteTag") ) } @@ -63,6 +83,10 @@ extension TagClient: DependencyKey { fetchTags: { tagRepository.fetchTags(descriptor: .init()) }, saveTag: { tagRepository.saveTag($0) }, updateTag: { tagRepository.updateTag($0, new: $1) }, - deleteTag: { tagRepository.deleteTag($0) } + deleteTag: { tagRepository.deleteTag($0) }, + + isValidatedSaveTag: { tagRepository.isValidatedSaveTag($0) }, + isValidatedUpdateTag: { tagRepository.isValidatedUpdateTag($0, new: $1) }, + isValidatedDeleteTag: { tagRepository.isValidatedDeleteTag($0) } ) } diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/TradeClient.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/TradeClient.swift index deb9074..7defe95 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/TradeClient.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/TradeClient.swift @@ -61,8 +61,8 @@ public extension DependencyValues { extension TradeClient: DependencyKey { public static var liveValue = TradeClient( fetchTrades: { tradeRepository.fetchTrades(descriptor: .init()) }, - saveTrade: { tradeRepository.saveTrade(dto: $0) }, - updateTrade: { tradeRepository.updateTrade(model:$0, dto: $1) }, - deleteTrade: { tradeRepository.deleteTrade(trade: $0) } + saveTrade: { tradeRepository.saveTrade($0) }, + updateTrade: { tradeRepository.updateTrade($0, new: $1) }, + deleteTrade: { tradeRepository.deleteTrade($0) } ) } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/TickerEditStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/TickerEditStore.swift index d1cef26..29c69fd 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/TickerEditStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/TickerEditStore.swift @@ -206,7 +206,7 @@ public struct TickerEditStore: Reducer { switch mode { case .add: - if let ticker = try? tickerClient.saveTicker(.init(type: tickerType, currency: currency, name: name)).get() { + if let ticker = try? tickerClient.saveTicker(.init(type: tickerType, currency: currency, name: name, tags: [])).get() { return .send(.delegate(.next(ticker))) } else { return .none @@ -214,7 +214,7 @@ public struct TickerEditStore: Reducer { case .edit: guard let ticker = ticker else { return .none } - if let ticker = try? tickerClient.updateTicker(ticker, .init(type: tickerType, currency: currency, name: name)).get() { + if let ticker = try? tickerClient.updateTicker(ticker, .init(type: tickerType, currency: currency, name: name, tags: [])).get() { return .send(.delegate(.save(ticker))) } else { return .none From 42c22755fe5ba96b5167661f0400c8c146026b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Sun, 1 Oct 2023 19:52:29 +0900 Subject: [PATCH 05/19] =?UTF-8?q?feat:=20trade=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Toolinder/App/Sources/RootApp.swift | 10 ---- .../Interface/Sources/DTO/TradeDTO.swift | 8 +-- .../Entity/TickerSummaryDataEntity.swift | 10 ++-- .../Entity/TickerTypeChartDataEntity.swift | 2 +- .../Sources/PersistentModel/Trade.swift | 6 +-- .../Sources/Repository/TradeRepository.swift | 16 +++--- .../Components/Cell/TradeItemCellView.swift | 2 +- .../Sources/EditTrade/TradeEditStore.swift | 49 ++++++++----------- .../Sources/EditTrade/TradeEditView.swift | 2 +- .../Sources/TradeDetail/TradeDetailView.swift | 2 +- 10 files changed, 45 insertions(+), 62 deletions(-) diff --git a/Projects/Toolinder/App/Sources/RootApp.swift b/Projects/Toolinder/App/Sources/RootApp.swift index 46fde9d..6bb1b1a 100644 --- a/Projects/Toolinder/App/Sources/RootApp.swift +++ b/Projects/Toolinder/App/Sources/RootApp.swift @@ -15,15 +15,6 @@ import ToolinderDomain @main struct RootApp: App { -// let modelContainer: ModelContainer - - init() { -// do { -// modelContainer = try ModelContainer(for: Ticker.self, Trade.self, Tag.self) -// } catch { -// fatalError("Could not initialize ModelContainer \(error)") -// } - } var body: some Scene { WindowGroup { RootView( @@ -34,6 +25,5 @@ struct RootApp: App { ) .onAppear(perform: UIApplication.shared.hideKeyboard) } -// .modelContainer(modelContainer) } } diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TradeDTO.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TradeDTO.swift index 22bb0a5..d4b9f9e 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TradeDTO.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TradeDTO.swift @@ -12,7 +12,7 @@ public struct TradeDTO { public let id: UUID public var side: TradeSide public var price: Double - public var volume: Double + public var quantity: Double public var fee: Double public var images: [Data] public var note: String @@ -24,7 +24,7 @@ public struct TradeDTO { id: UUID = .init(), side: TradeSide = .buy, price: Double = 0, - volume: Double = 0, + quantity: Double = 0, fee: Double = 0, images: [Data] = [], note: String = "", @@ -35,7 +35,7 @@ public struct TradeDTO { self.side = side self.images = images self.price = price - self.volume = volume + self.quantity = quantity self.fee = fee self.note = note self.date = date @@ -47,7 +47,7 @@ public struct TradeDTO { id: id, side: side, price: price, - volume: volume, + quantity: quantity, fee: fee, images: images, note: note, diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/Entity/TickerSummaryDataEntity.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/Entity/TickerSummaryDataEntity.swift index 9cc31ed..f9343d4 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/Entity/TickerSummaryDataEntity.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/Entity/TickerSummaryDataEntity.swift @@ -31,12 +31,12 @@ public extension Ticker { for trade in trades { switch trade.side { case .buy: - totalAmount += trade.price * trade.volume - buyVolume += trade.volume + totalAmount += trade.price * trade.quantity + buyVolume += trade.quantity case .sell: - totalAmount -= avgPrice * trade.volume - sellVoume += trade.volume + totalAmount -= avgPrice * trade.quantity + sellVoume += trade.quantity } totalVolume = buyVolume - sellVoume @@ -47,7 +47,7 @@ public extension Ticker { // 수익 계산 if trade.side == .sell { - profit += (trade.price - avgPrice) * trade.volume + profit += (trade.price - avgPrice) * trade.quantity } } // 수익률 계산 diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/Entity/TickerTypeChartDataEntity.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/Entity/TickerTypeChartDataEntity.swift index f45e408..60aaba1 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/Entity/TickerTypeChartDataEntity.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/Entity/TickerTypeChartDataEntity.swift @@ -19,7 +19,7 @@ public extension [Trade] { return TickerType.allCases.map { type in let trades = self.filter({ $0.ticker?.type == type }) let hold = trades.map { trade in - let sum: Double = trade.price * trade.volume + let sum: Double = trade.price * trade.quantity let sign: Double = trade.side == .buy ? 1.0 : -1.0 return sum * sign diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Trade.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Trade.swift index 4098e56..8257fc1 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Trade.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/PersistentModel/Trade.swift @@ -13,7 +13,7 @@ public class Trade { public let id: UUID = UUID() public var side: TradeSide = TradeSide.buy public var price: Double = 0 - public var volume: Double = 0 + public var quantity: Double = 0 public var fee: Double = 0 public var images: [Data] = [] public var note: String = "" @@ -25,7 +25,7 @@ public class Trade { id: UUID = .init(), side: TradeSide, price: Double, - volume: Double, + quantity: Double, fee: Double, images: [Data], note: String, @@ -36,7 +36,7 @@ public class Trade { self.side = side self.images = images self.price = price - self.volume = volume + self.quantity = quantity self.fee = fee self.note = note self.date = date diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TradeRepository.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TradeRepository.swift index bf53051..b34b012 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TradeRepository.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TradeRepository.swift @@ -49,7 +49,7 @@ public class TradeRepository: TradeRepositoryInterface { trade.date = newTrade.date trade.side = newTrade.side trade.price = newTrade.price - trade.volume = newTrade.volume + trade.quantity = newTrade.quantity trade.images = newTrade.images trade.note = newTrade.note return .success(trade) @@ -74,13 +74,13 @@ public class TradeRepository: TradeRepositoryInterface { let currentVolume = trades.reduce(0) { (result, trade) in if trade.side == .buy { - return result + trade.volume + return result + trade.quantity } else { - return result - trade.volume + return result - trade.quantity } } - return currentVolume - trade.volume >= 0 + return currentVolume - trade.quantity >= 0 } public func isValidatedUpdateTrade(_ trade: Trade, new newTrade: TradeDTO) -> Bool { @@ -91,9 +91,9 @@ public class TradeRepository: TradeRepositoryInterface { var currentVolume = 0.0 for tmpTrade in trades { if trade.side == .buy { - currentVolume += tmpTrade == trade ? newTrade.volume : tmpTrade.volume + currentVolume += tmpTrade == trade ? newTrade.quantity : tmpTrade.quantity } else { - currentVolume -= tmpTrade == trade ? newTrade.volume : tmpTrade.volume + currentVolume -= tmpTrade == trade ? newTrade.quantity : tmpTrade.quantity } if currentVolume < 0 { @@ -112,9 +112,9 @@ public class TradeRepository: TradeRepositoryInterface { var currentVolume = 0.0 for tmpTrade in trades { if trade.side == .buy { - currentVolume += tmpTrade == trade ? 0 : tmpTrade.volume + currentVolume += tmpTrade == trade ? 0 : tmpTrade.quantity } else { - currentVolume -= tmpTrade == trade ? 0 : tmpTrade.volume + currentVolume -= tmpTrade == trade ? 0 : tmpTrade.quantity } if currentVolume < 0 { diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TradeItemCellView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TradeItemCellView.swift index 1fa4894..69cd08e 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TradeItemCellView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TradeItemCellView.swift @@ -74,7 +74,7 @@ public struct TradeItemCellView: View { Text("\(Int(viewStore.state.trade.price)) \(viewStore.state.trade.ticker?.currency.rawValue ?? "")") .font(.caption) - Text("\(Int(viewStore.state.trade.volume)) vol") + Text("\(Int(viewStore.state.trade.quantity)) vol") .font(.caption) } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/TradeEditStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/TradeEditStore.swift index b7fd8bc..7f08a27 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/TradeEditStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/TradeEditStore.swift @@ -27,7 +27,7 @@ public struct TradeEditStore: Reducer { public var selectedTrade: Trade? public var price: Double - public var volume: Double + public var quantity: Double public var fee: Double public var selectedDate: Date = .now public var selectedTradeSide: TradeSide = .buy @@ -48,7 +48,7 @@ public struct TradeEditStore: Reducer { self.selectedTicker = selectedTicker self.selectedTrade = selectedTrade self.price = selectedTrade?.price ?? 0 - self.volume = selectedTrade?.volume ?? 0 + self.quantity = selectedTrade?.quantity ?? 0 self.fee = selectedTrade?.fee ?? 0 self.selectedDate = selectedTrade?.date ?? selectedDate self.note = selectedTrade?.note ?? "" @@ -60,7 +60,7 @@ public struct TradeEditStore: Reducer { case onAppear case setPrice(Double) - case setVolume(Double) + case setQuantity(Double) case setFee(Double) case selectDate(Date) case selectTradeSide(TradeSide) @@ -98,8 +98,8 @@ public struct TradeEditStore: Reducer { state.price = price return .none - case let .setVolume(volume): - state.volume = volume + case let .setQuantity(volume): + state.quantity = volume return .none case let .setFee(fee): @@ -135,7 +135,7 @@ public struct TradeEditStore: Reducer { trade: state.selectedTrade, side: state.selectedTradeSide, price: state.price, - volume: state.volume, + quantity: state.quantity, fee: state.fee, images: state.images, note: state.note, @@ -172,7 +172,7 @@ public struct TradeEditStore: Reducer { trade: Trade? = nil, side: TradeSide, price: Double, - volume: Double, + quantity: Double, fee: Double, images: [Data], note: String, @@ -180,33 +180,26 @@ public struct TradeEditStore: Reducer { ticker: Ticker ) -> Effect { guard !price.isZero else { return .none } - guard !volume.isZero else { return .none } + guard !quantity.isZero else { return .none } guard fee < 100 else { return .none } + let tradeDTO = TradeDTO( + side: side, + price: price, + quantity: quantity, + fee: fee, + images: images, + note: note, + date: date, + ticker: ticker + ) + if let unSavedTrade = trade { - if let trade = try? tradeClient.updateTrade(unSavedTrade, .init( - side: side, - price: price, - volume: volume, - fee: fee, - images: images, - note: note, - date: date, - ticker: ticker - )).get() { + if let trade = try? tradeClient.updateTrade(unSavedTrade, tradeDTO).get() { return .send(.delegate(.save(trade))) } } else { - if let trade = try? tradeClient.saveTrade(.init( - side: side, - price: price, - volume: volume, - fee: fee, - images: images, - note: note, - date: date, - ticker: ticker - )).get() { + if let trade = try? tradeClient.saveTrade(tradeDTO).get() { return .send(.delegate(.save(trade))) } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/TradeEditView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/TradeEditView.swift index c621e53..d5ab19c 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/TradeEditView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/TradeEditView.swift @@ -99,7 +99,7 @@ public struct TradeEditView: View { Image(systemName: "plusminus.circle.fill") - TextField("Volume", value: viewStore.binding(get: \.volume, send: TradeEditStore.Action.setVolume), format: .number) + TextField("Volume", value: viewStore.binding(get: \.quantity, send: TradeEditStore.Action.setQuantity), format: .number) .keyboardType(.decimalPad) Image(systemName: "building.columns.circle.fill") diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/TradeDetail/TradeDetailView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/TradeDetail/TradeDetailView.swift index 873a43f..f2c9b40 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/TradeDetail/TradeDetailView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/TradeDetail/TradeDetailView.swift @@ -80,7 +80,7 @@ public struct TradeDetailView: View { HStack(alignment: .bottom, spacing: .zero) { Spacer() - Text(scaledString(valueOrNil: viewStore.state.trade.volume)) + Text(scaledString(valueOrNil: viewStore.state.trade.quantity)) .font(.title) .fontWeight(.semibold) From c97d93c525b075e3e9035a5eb99cccd4f050dd39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Mon, 2 Oct 2023 12:44:00 +0900 Subject: [PATCH 06/19] =?UTF-8?q?feat:=20tradeEdit=20->=20editTrade=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Color+Extension.swift | 46 +++++++++ .../Sources/Calendar/CalendarStore.swift | 48 +++++----- .../Sources/Calendar/CalendarView.swift | 12 +-- .../CalendarMain/CalendarMainStore.swift | 4 +- .../Components/Cell/TagItemCellStore.swift | 60 ++++++++++++ .../Components/Cell/TagItemCellView.swift | 49 ++++++++++ .../Components/View/EditHeaderView.swift | 66 +++++++++++++ .../Sources/EditTag/EditTagStore.swift | 76 +++++++++++++++ .../Sources/EditTag/EditTagView.swift | 85 +++++++++++++++++ ...rEditStore.swift => EditTickerStore.swift} | 69 ++++++++------ ...kerEditView.swift => EditTickerView.swift} | 93 +++++++++++++------ ...deEditStore.swift => EditTradeStore.swift} | 4 +- ...radeEditView.swift => EditTradeView.swift} | 26 +++--- .../Sources/SelectTag/SelectTagStore.swift | 45 ++++++++- .../Sources/SelectTag/SelectTagView.swift | 67 ++++++++----- .../TickerDetail/TickerDetailStore.swift | 18 ++-- .../TickerDetail/TickerDetailView.swift | 6 +- .../TradeDetail/TradeDetailStore.swift | 24 ++--- .../Sources/TradeDetail/TradeDetailView.swift | 6 +- 19 files changed, 645 insertions(+), 159 deletions(-) create mode 100644 Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellStore.swift create mode 100644 Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellView.swift create mode 100644 Projects/Toolinder/Feature/Trade/Interface/Sources/Components/View/EditHeaderView.swift create mode 100644 Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagStore.swift create mode 100644 Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagView.swift rename Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/{TickerEditStore.swift => EditTickerStore.swift} (79%) rename Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/{TickerEditView.swift => EditTickerView.swift} (50%) rename Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/{TradeEditStore.swift => EditTradeStore.swift} (98%) rename Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/{TradeEditView.swift => EditTradeView.swift} (84%) diff --git a/Projects/Folio/Shared/DesignSystem/Sources/Color+Extension.swift b/Projects/Folio/Shared/DesignSystem/Sources/Color+Extension.swift index 3bbeee9..1933ce4 100644 --- a/Projects/Folio/Shared/DesignSystem/Sources/Color+Extension.swift +++ b/Projects/Folio/Shared/DesignSystem/Sources/Color+Extension.swift @@ -8,7 +8,53 @@ import SwiftUI public extension Color { + init(hex: String) { + let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) + var int: UInt64 = 0 + Scanner(string: hex).scanHexInt64(&int) + let a, r, g, b: UInt64 + switch hex.count { + case 3: // RGB (12-bit) + (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) + case 6: // RGB (24-bit) + (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) + case 8: // ARGB (32-bit) + (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) + default: + (a, r, g, b) = (1, 1, 1, 0) + } + + self.init( + .sRGB, + red: Double(r) / 255, + green: Double(g) / 255, + blue: Double(b) / 255, + opacity: Double(a) / 255 + ) + } + static func blackOrWhite(_ isSelected: Bool = false) -> Self { return isSelected ? Color(uiColor: .label) : Color(uiColor: .systemBackground) } + + func toHex() -> String { + let uic = UIColor(self) + guard let components = uic.cgColor.components, components.count >= 3 else { + return "" + } + let r = Float(components[0]) + let g = Float(components[1]) + let b = Float(components[2]) + var a = Float(1.0) + + if components.count >= 4 { + a = Float(components[3]) + } + + if a != Float(1.0) { + return String(format: "%02lX%02lX%02lX%02lX", lroundf(r * 255), lroundf(g * 255), lroundf(b * 255), lroundf(a * 255)) + } else { + return String(format: "%02lX%02lX%02lX", lroundf(r * 255), lroundf(g * 255), lroundf(b * 255)) + } + } } diff --git a/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarStore.swift b/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarStore.swift index 30ebace..c3a5cd6 100644 --- a/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarStore.swift +++ b/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarStore.swift @@ -43,8 +43,8 @@ public struct CalendarStore: Reducer { public var calendarItem: IdentifiedArrayOf = [] public var tradeItem: IdentifiedArrayOf = [] - @PresentationState var tickerEdit: TickerEditStore.State? - @PresentationState var tradeEdit: TradeEditStore.State? + @PresentationState var editTicker: EditTickerStore.State? + @PresentationState var editTrade: EditTradeStore.State? public init( id: UUID = .init(), @@ -83,8 +83,8 @@ public struct CalendarStore: Reducer { case calendarItem(id: CalendarItemCellStore.State.ID, action: CalendarItemCellStore.Action) case tradeItem(id: TradeItemCellStore.State.ID, action: TradeItemCellStore.Action) - case tickerEdit(PresentationAction) - case tradeEdit(PresentationAction) + case editTicker(PresentationAction) + case editTrade(PresentationAction) case delegate(Delegate) @@ -104,7 +104,7 @@ public struct CalendarStore: Reducer { return .none case .newButtonTapped: - state.tickerEdit = .init() + state.editTicker = .init() return .none case let .tradeItemTapped(trade): @@ -142,31 +142,31 @@ public struct CalendarStore: Reducer { return .none } - case .tickerEdit(.presented(.delegate(.cancel))): - state.tickerEdit = nil + case .editTicker(.presented(.delegate(.cancel))): + state.editTicker = nil return .none - case let .tickerEdit(.presented(.delegate(.next(ticker)))): - state.tickerEdit = nil - state.tradeEdit = .init(selectedTicker: ticker, selectedDate: state.selectedDate) + case let .editTicker(.presented(.delegate(.next(ticker)))): + state.editTicker = nil + state.editTrade = .init(selectedTicker: ticker, selectedDate: state.selectedDate) return .none - case .tickerEdit(.dismiss): - state.tickerEdit = nil + case .editTicker(.dismiss): + state.editTicker = nil return .none - case .tradeEdit(.presented(.delegate(.save))): - state.tickerEdit = nil - state.tradeEdit = nil + case .editTrade(.presented(.delegate(.save))): + state.editTicker = nil + state.editTrade = nil return .none - case let .tradeEdit(.presented(.delegate(.cancel(ticker)))): - state.tickerEdit = .init(selectedTicker: ticker) - state.tradeEdit = nil + case let .editTrade(.presented(.delegate(.cancel(ticker)))): + state.editTicker = .init(selectedTicker: ticker) + state.editTrade = nil return .none - case .tradeEdit(.dismiss): - state.tradeEdit = nil + case .editTrade(.dismiss): + state.editTrade = nil return .none default: @@ -179,11 +179,11 @@ public struct CalendarStore: Reducer { .forEach(\.tradeItem, action: /Action.tradeItem(id:action:)) { TradeItemCellStore() } - .ifLet(\.$tickerEdit, action: /Action.tickerEdit) { - TickerEditStore() + .ifLet(\.$editTicker, action: /Action.editTicker) { + EditTickerStore() } - .ifLet(\.$tradeEdit, action: /Action.tradeEdit) { - TradeEditStore() + .ifLet(\.$editTrade, action: /Action.editTrade) { + EditTradeStore() } } } diff --git a/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarView.swift b/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarView.swift index 666d93f..172d8e5 100644 --- a/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarView.swift +++ b/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarView.swift @@ -48,20 +48,20 @@ public struct CalendarView: View { } .sheet( store: self.store.scope( - state: \.$tickerEdit, - action: { .tickerEdit($0) } + state: \.$editTicker, + action: { .editTicker($0) } ) ) { - TickerEditView(store: $0) + EditTickerView(store: $0) .presentationDetents([.medium]) } .sheet( store: self.store.scope( - state: \.$tradeEdit, - action: { .tradeEdit($0) } + state: \.$editTrade, + action: { .editTrade($0) } ) ) { - TradeEditView(store: $0) + EditTradeView(store: $0) .presentationDetents([.medium]) } .tag(viewStore.offset) diff --git a/Projects/Toolinder/Feature/Calendar/Interface/Sources/CalendarMain/CalendarMainStore.swift b/Projects/Toolinder/Feature/Calendar/Interface/Sources/CalendarMain/CalendarMainStore.swift index 5c83817..010a35d 100644 --- a/Projects/Toolinder/Feature/Calendar/Interface/Sources/CalendarMain/CalendarMainStore.swift +++ b/Projects/Toolinder/Feature/Calendar/Interface/Sources/CalendarMain/CalendarMainStore.swift @@ -123,10 +123,10 @@ public struct CalendarMainStore: Reducer { case .delegate(.refresh): return .send(.fetch) - case .tradeEdit(.presented(.delegate(.save))): + case .editTrade(.presented(.delegate(.save))): return .send(.fetch) - case .tradeEdit(.dismiss): + case .editTrade(.dismiss): return .send(.fetch) case let .delegate(.detail(trade)): diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellStore.swift new file mode 100644 index 0000000..02e6c11 --- /dev/null +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellStore.swift @@ -0,0 +1,60 @@ +// +// TagItemCellStore.swift +// ToolinderFeatureTradeInterface +// +// Created by 송영모 on 2023/10/01. +// + +import Foundation + +import ComposableArchitecture + +import ToolinderDomain + +public struct TagItemCellStore: Reducer { + public init() {} + + public struct State: Equatable, Identifiable { + public let id: UUID + + public let tag: Tag + public var isSelected: Bool + + public init( + id: UUID = .init(), + tag: Tag, + isSelected: Bool = false + ) { + self.id = id + self.tag = tag + self.isSelected = isSelected + } + } + + public enum Action: Equatable { + case onAppear + + case tapped + + case delegate(Delegate) + + public enum Delegate: Equatable { + case tapped + } + } + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case .onAppear: + return .none + + case .tapped: + return .send(.delegate(.tapped)) + + default: + return .none + } + } + } +} diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellView.swift new file mode 100644 index 0000000..1fa18dd --- /dev/null +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellView.swift @@ -0,0 +1,49 @@ +// +// TagItemCellView.swift +// ToolinderFeatureTradeInterface +// +// Created by 송영모 on 2023/10/01. +// + +import SwiftUI + +import ComposableArchitecture + +import ToolinderDomain +import ToolinderShared + +public struct TagItemCellView: View { + private let store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + WithViewStore(self.store, observe: { $0 }) { viewStore in + HStack(spacing: 2) { + Circle() + .fill(Color(hex: viewStore.state.tag.hex)) + +// RoundedRectangle(cornerRadius: 3) +// .fill(viewStore.state.trade.side == .buy ? .pink : .mint) +// .frame(width: 2.5, height: 11) + +// Text(viewStore.state.trade.ticker?.name ?? "") +// .font(.caption2) +// .fontWeight(.light) +// .foregroundStyle(Color.blackOrWhite(!viewStore.state.isSelected)) + + Spacer() + } + .padding(10) + .background(Color(uiColor: .systemGray6)) + .clipShape( + RoundedRectangle( + cornerRadius: 8, + style: .continuous + ) + ) + } + } +} diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/View/EditHeaderView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/View/EditHeaderView.swift new file mode 100644 index 0000000..b832b52 --- /dev/null +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/View/EditHeaderView.swift @@ -0,0 +1,66 @@ +// +// EditHeaderView.swift +// ToolinderFeatureTradeInterface +// +// Created by 송영모 on 2023/10/02. +// + +import Foundation +import SwiftUI + +public enum EditMode { + case add + case bypassAdd + case edit + + public enum Action { + case dismiss + case delete + } +} + +public struct EditHeaderView: View { + public let mode: EditMode + public let title: LocalizedStringKey + + public var action: (EditMode.Action) -> () + + public init( + mode: EditMode, + title: LocalizedStringKey, + action: @escaping (EditMode.Action) -> Void + ) { + self.mode = mode + self.title = title + self.action = action + } + + public var body: some View { + HStack { + if mode == .add { + Button(action: { + action(.dismiss) + }, label: { + Image(systemName: "chevron.left") + .font(.title) + .foregroundStyle(.foreground) + }) + } + + Text(title) + .font(.title) + + Spacer() + + if mode == .edit { + Button(action: { + action(.delete) + }, label: { + Image(systemName: "trash.circle.fill") + .foregroundStyle(.foreground) + .font(.title) + }) + } + } + } +} diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagStore.swift new file mode 100644 index 0000000..083c270 --- /dev/null +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagStore.swift @@ -0,0 +1,76 @@ +// +// EditTagStore.swift +// ToolinderFeatureTradeInterface +// +// Created by 송영모 on 2023/10/02. +// + +import Foundation +import SwiftUI + +import ComposableArchitecture + +import ToolinderDomain + +public struct EditTagStore: Reducer { + public init() {} + + public struct State: Equatable { + public var mode: EditMode + public var tag: Tag? + + public var title: LocalizedStringKey = "" + + public var tagName: String = "" + public var tagColor: Color = .blackOrWhite() + + public init( + mode: EditMode, + tag: Tag? = nil + ) { + self.mode = mode + self.tag = tag + } + } + + public enum Action: Equatable { + case onAppear + + case setTagName(String) + case setTagColor(Color) + case dismissButtonTapped + case deleteButtonTapped + case confirmButtonTapped + + case delegate(Delegate) + + public enum Delegate: Equatable { + case confirm + } + } + + @Dependency(\.tagClient) var tagClient + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case .onAppear: + return .none + + case let .setTagName(name): + state.tagName = name + return .none + + case let .setTagColor(color): + state.tagColor = color + return .none + + case .confirmButtonTapped: + return .none + + default: + return .none + } + } + } +} diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagView.swift new file mode 100644 index 0000000..4a3104a --- /dev/null +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagView.swift @@ -0,0 +1,85 @@ +// +// EditTagView.swift +// ToolinderFeatureTradeInterface +// +// Created by 송영모 on 2023/10/02. +// + +import Foundation +import SwiftUI + +import ComposableArchitecture + +import ToolinderDomain +import ToolinderShared + +public struct EditTagView: View { + let store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + WithViewStore(self.store, observe: { $0 }) { viewStore in + ScrollView { + VStack(alignment: .leading) { + EditHeaderView(mode: viewStore.state.mode, title: "") { action in + switch action { + case .dismiss: + viewStore.send(.dismissButtonTapped) + case .delete: + viewStore.send(.deleteButtonTapped) + } + } + + nameView(viewStore: viewStore) + + colorView(viewStore: viewStore) + + MinimalButton(title: "Confirm") { + viewStore.send(.confirmButtonTapped) + } + } + .padding() + } + } + } + + private func headerView(viewStore: ViewStoreOf) -> some View { + HStack { + if viewStore.state.mode == .add { + Button(action: { + viewStore.send(.dismissButtonTapped) + }, label: { + Image(systemName: "chevron.left") + .font(.title) + .foregroundStyle(.foreground) + }) + } + + Spacer() + + if viewStore.state.mode == .edit { + Button(action: { + viewStore.send(.deleteButtonTapped) + }, label: { + Image(systemName: "trash.circle.fill") + .foregroundStyle(.foreground) + .font(.title) + }) + } + } + } + + private func nameView(viewStore: ViewStoreOf) -> some View { + TextField("Name", text: viewStore.binding(get: \.tagName, send: EditTagStore.Action.setTagName)) + .foregroundStyle(.foreground) + } + + private func colorView(viewStore: ViewStoreOf) -> some View { + ColorPicker(selection: viewStore.binding(get: \.tagColor, send: EditTagStore.Action.setTagColor), label: { + Label("Color", systemImage: "paintpalette.fill") + }) + } +} diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/TickerEditStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift similarity index 79% rename from Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/TickerEditStore.swift rename to Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift index 29c69fd..32966b1 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/TickerEditStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift @@ -11,7 +11,7 @@ import ComposableArchitecture import ToolinderDomainTradeInterface -public struct TickerEditStore: Reducer { +public struct EditTickerStore: Reducer { public init() {} public enum Mode { @@ -22,14 +22,17 @@ public struct TickerEditStore: Reducer { public struct State: Equatable { public var mode: Mode public var name: String = "" - public var tickerType: TickerType? - public var currency: Currency? + public var selectedTickerType: TickerType? + public var selectedCurrency: Currency? + public var selectedTags: [Tag] = [] public var selectedTicker: Ticker? public var tickerItem: IdentifiedArrayOf = [] + public var tagItem: IdentifiedArrayOf = [] @PresentationState var selectTickerType: SelectTickerTypeStore.State? @PresentationState var selectCurrency: SelectCurrencyStore.State? + @PresentationState var selectTag: SelectTagStore.State? @PresentationState var alert: AlertState? public init( @@ -41,8 +44,13 @@ public struct TickerEditStore: Reducer { if mode == .edit { self.name = selectedTicker?.name ?? "" - self.tickerType = selectedTicker?.type ?? .stock - self.currency = selectedTicker?.currency ?? .dollar + self.selectedTickerType = selectedTicker?.type ?? .stock + self.selectedCurrency = selectedTicker?.currency ?? .dollar + self.tagItem = .init( + uniqueElements: selectedTicker?.tags?.map { tag in + return .init(tag: tag) + } ?? [] + ) } } } @@ -51,9 +59,9 @@ public struct TickerEditStore: Reducer { case onAppear case setName(String) - case tickerTapped(Ticker) - case tickerTypeViewTapped - case currencyViewTapped + case tickerTypeButtonTapped + case currencyButtonTapped + case tagButtonTapped case deleteButtonTapped case nextButtonTapped @@ -61,8 +69,10 @@ public struct TickerEditStore: Reducer { case fetchTickersResponse([Ticker]) case tickerItem(id: TickerItemCellStore.State.ID, action: TickerItemCellStore.Action) + case tagItem(id: TagItemCellStore.State.ID, action: TagItemCellStore.Action) case selectTickerType(PresentationAction) case selectCurrency(PresentationAction) + case selectTag(PresentationAction) case alert(PresentationAction) case delegate(Delegate) @@ -93,23 +103,18 @@ public struct TickerEditStore: Reducer { state.name = name return .none - case let .tickerTapped(ticker): - if ticker == state.selectedTicker { - state.selectedTicker = nil - } else { - state.selectedTicker = ticker - } - - return .none - - case .tickerTypeViewTapped: + case .tickerTypeButtonTapped: state.selectTickerType = .init() return .none - case .currencyViewTapped: + case .currencyButtonTapped: state.selectCurrency = .init() return .none + case .tagButtonTapped: + state.selectTag = .init(selectedTags: state.selectedTags) + return .none + case .deleteButtonTapped: state.alert = AlertState { TextState("\(state.selectedTicker?.trades?.count ?? 0) records are also deleted.") @@ -128,8 +133,8 @@ public struct TickerEditStore: Reducer { return validateAndSaveTickerEffect( mode: state.mode, ticker: state.selectedTicker, - tickerType: state.tickerType, - currency: state.currency, + tickerType: state.selectedTickerType, + currency: state.selectedCurrency, name: state.name ) @@ -138,7 +143,11 @@ public struct TickerEditStore: Reducer { return .send(.fetchTickersResponse(tickers)) case let .fetchTickersResponse(tickers): - state.tickerItem = .init(uniqueElements: tickers.map { .init(mode: .preview, ticker: $0) }) + state.tickerItem = .init( + uniqueElements: tickers.map { + .init(mode: .preview, ticker: $0) + } + ) return .none case let .tickerItem(id: id, action: .delegate(.tapped)): @@ -160,20 +169,24 @@ public struct TickerEditStore: Reducer { case let .selectTickerType(.presented(.delegate(.select(tickerType)))): state.selectTickerType = nil - state.tickerType = tickerType + state.selectedTickerType = tickerType + return .none + + case let .selectCurrency(.presented(.delegate(.select(currency)))): + state.selectCurrency = nil + state.selectedCurrency = currency return .none case .selectTickerType(.dismiss): state.selectTickerType = nil return .none - case let .selectCurrency(.presented(.delegate(.select(currency)))): + case .selectCurrency(.dismiss): state.selectCurrency = nil - state.currency = currency return .none - case .selectCurrency(.dismiss): - state.selectCurrency = nil + case .selectTag(.dismiss): + state.selectTag = nil return .none case .alert(.presented(.confirmDeletion)): @@ -199,7 +212,7 @@ public struct TickerEditStore: Reducer { tickerType: TickerType?, currency: Currency?, name: String - ) -> Effect { + ) -> Effect { guard let tickerType = tickerType else { return .none } guard let currency = currency else { return .none } guard name.isEmpty == false else { return .none } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/TickerEditView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerView.swift similarity index 50% rename from Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/TickerEditView.swift rename to Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerView.swift index 161c5dd..4473a5c 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/TickerEditView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerView.swift @@ -13,10 +13,10 @@ import ComposableArchitecture import ToolinderDomainTradeInterface import ToolinderShared -public struct TickerEditView: View { - let store: StoreOf +public struct EditTickerView: View { + let store: StoreOf - public init(store: StoreOf) { + public init(store: StoreOf) { self.store = store } @@ -33,7 +33,16 @@ public struct TickerEditView: View { Divider() .padding(.horizontal) - inputView(viewStore: viewStore) + nameView(viewStore: viewStore) + .padding(.horizontal) + + tickerTypeView(viewStore: viewStore) + .padding(.horizontal) + + currencyView(viewStore: viewStore) + .padding(.horizontal) + + tagView(viewStore: viewStore) .padding(.horizontal) Spacer() @@ -51,8 +60,8 @@ public struct TickerEditView: View { state: \.$selectCurrency, action: { .selectCurrency($0) } ) - ) { store in - SelectCurrencyView(store: store) + ) { + SelectCurrencyView(store: $0) .presentationDetents([.medium]) } .sheet( @@ -60,8 +69,17 @@ public struct TickerEditView: View { state: \.$selectTickerType, action: { .selectTickerType($0) } ) - ) { store in - SelectTickerTypeView(store: store) + ) { + SelectTickerTypeView(store: $0) + .presentationDetents([.medium]) + } + .sheet( + store: self.store.scope( + state: \.$selectTag, + action: { .selectTag($0) } + ) + ) { + SelectTagView(store: $0) .presentationDetents([.medium]) } .alert( @@ -73,7 +91,7 @@ public struct TickerEditView: View { } } - private func headerView(viewStore: ViewStoreOf) -> some View { + private func headerView(viewStore: ViewStoreOf) -> some View { HStack { if viewStore.state.mode == .add { Text("Ticker") @@ -97,10 +115,10 @@ public struct TickerEditView: View { } } - private func tickersView(viewStore: ViewStoreOf) -> some View { + private func tickersView(viewStore: ViewStoreOf) -> some View { ScrollView(.horizontal) { HStack { - ForEachStore(self.store.scope(state: \.tickerItem, action: TickerEditStore.Action.tickerItem(id:action:))) { + ForEachStore(self.store.scope(state: \.tickerItem, action: EditTickerStore.Action.tickerItem(id:action:))) { TickerItemCellView(store: $0) } } @@ -108,22 +126,43 @@ public struct TickerEditView: View { } } - private func inputView(viewStore: ViewStoreOf) -> some View { - VStack(alignment: .leading, spacing: 20) { - TextField("Name", text: viewStore.binding(get: \.name, send: TickerEditStore.Action.setName)) - - Button(action: { - viewStore.send(.tickerTypeViewTapped) - }, label: { - Label(viewStore.tickerType?.rawValue ?? "Ticker Type", systemImage: viewStore.tickerType?.systemImageName ?? "questionmark.circle.fill") - }) - - Button(action: { - viewStore.send(.currencyViewTapped) - }, label: { - Label(viewStore.currency?.rawValue ?? "Currency", systemImage: viewStore.currency?.systemImageName ?? "questionmark.circle.fill") - }) + private func nameView(viewStore: ViewStoreOf) -> some View { + TextField("Name", text: viewStore.binding(get: \.name, send: EditTickerStore.Action.setName)) + .foregroundStyle(.foreground) + } + + private func tickerTypeView(viewStore: ViewStoreOf) -> some View { + Button(action: { + viewStore.send(.tickerTypeButtonTapped) + }, label: { + Label(viewStore.selectedTickerType?.rawValue ?? "Ticker Type", systemImage: viewStore.selectedTickerType?.systemImageName ?? "questionmark.circle.fill") + .foregroundStyle(.foreground) + }) + } + + private func currencyView(viewStore: ViewStoreOf) -> some View { + Button(action: { + viewStore.send(.currencyButtonTapped) + }, label: { + Label(viewStore.selectedCurrency?.rawValue ?? "Currency", systemImage: viewStore.selectedCurrency?.systemImageName ?? "questionmark.circle.fill") + .foregroundStyle(.foreground) + }) + } + + private func tagView(viewStore: ViewStoreOf) -> some View { + ScrollView(.horizontal) { + HStack { + Button(action: { + viewStore.send(.tagButtonTapped) + }, label: { + Label("Tag", systemImage: "tag.circle.fill") + .foregroundStyle(.foreground) + }) + + ForEachStore(self.store.scope(state: \.tagItem, action: EditTickerStore.Action.tagItem(id:action:))) { + TagItemCellView(store: $0) + } + } } - .foregroundStyle(.foreground) } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/TradeEditStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/EditTradeStore.swift similarity index 98% rename from Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/TradeEditStore.swift rename to Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/EditTradeStore.swift index 7f08a27..98e926c 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/TradeEditStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/EditTradeStore.swift @@ -12,7 +12,7 @@ import ComposableArchitecture import ToolinderDomain -public struct TradeEditStore: Reducer { +public struct EditTradeStore: Reducer { public init() {} public enum Mode { @@ -178,7 +178,7 @@ public struct TradeEditStore: Reducer { note: String, date: Date, ticker: Ticker - ) -> Effect { + ) -> Effect { guard !price.isZero else { return .none } guard !quantity.isZero else { return .none } guard fee < 100 else { return .none } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/TradeEditView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/EditTradeView.swift similarity index 84% rename from Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/TradeEditView.swift rename to Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/EditTradeView.swift index d5ab19c..3b244e4 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/TradeEditView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTrade/EditTradeView.swift @@ -14,10 +14,10 @@ import ComposableArchitecture import ToolinderDomain import ToolinderShared -public struct TradeEditView: View { - let store: StoreOf +public struct EditTradeView: View { + let store: StoreOf - public init(store: StoreOf) { + public init(store: StoreOf) { self.store = store } @@ -46,7 +46,7 @@ public struct TradeEditView: View { } } - private func headerView(viewStore: ViewStoreOf) -> some View { + private func headerView(viewStore: ViewStoreOf) -> some View { HStack { if viewStore.state.mode == .add { Button(action: { @@ -75,9 +75,9 @@ public struct TradeEditView: View { } } - private func pickerView(viewStore: ViewStoreOf) -> some View { + private func pickerView(viewStore: ViewStoreOf) -> some View { HStack { - Picker("", selection: viewStore.binding(get: \.selectedTradeSide, send: TradeEditStore.Action.selectTradeSide)) { + Picker("", selection: viewStore.binding(get: \.selectedTradeSide, send: EditTradeStore.Action.selectTradeSide)) { ForEach(TradeSide.allCases, id: \.self) { tradeSide in Text(tradeSide.rawValue) .tag(tradeSide) @@ -85,26 +85,26 @@ public struct TradeEditView: View { } .pickerStyle(.segmented) - DatePicker("", selection: viewStore.binding(get: \.selectedDate, send: TradeEditStore.Action.selectDate)) + DatePicker("", selection: viewStore.binding(get: \.selectedDate, send: EditTradeStore.Action.selectDate)) } } - private func inputView(viewStore: ViewStoreOf) -> some View { + private func inputView(viewStore: ViewStoreOf) -> some View { VStack(spacing: 20) { HStack { viewStore.state.selectedTicker.currency.image - TextField("Price", value: viewStore.binding(get: \.price, send: TradeEditStore.Action.setPrice), format: .number) + TextField("Price", value: viewStore.binding(get: \.price, send: EditTradeStore.Action.setPrice), format: .number) .keyboardType(.decimalPad) Image(systemName: "plusminus.circle.fill") - TextField("Volume", value: viewStore.binding(get: \.quantity, send: TradeEditStore.Action.setQuantity), format: .number) + TextField("Volume", value: viewStore.binding(get: \.quantity, send: EditTradeStore.Action.setQuantity), format: .number) .keyboardType(.decimalPad) Image(systemName: "building.columns.circle.fill") - TextField("Fee %", value: viewStore.binding(get: \.fee, send: TradeEditStore.Action.setFee), format: .number) + TextField("Fee %", value: viewStore.binding(get: \.fee, send: EditTradeStore.Action.setFee), format: .number) .keyboardType(.decimalPad) Spacer() @@ -113,7 +113,7 @@ public struct TradeEditView: View { VStack(alignment: .leading) { Image(systemName: "note.text") - TextEditor(text: viewStore.binding(get: \.note, send: TradeEditStore.Action.setNote)) + TextEditor(text: viewStore.binding(get: \.note, send: EditTradeStore.Action.setNote)) } ScrollView(.horizontal) { @@ -124,7 +124,7 @@ public struct TradeEditView: View { ImageItem(imageData: imageData) } - PhotosPicker(selection: viewStore.binding(get: \.selectedPhotosPickerItems, send: TradeEditStore.Action.setPhotoPickerItems), + PhotosPicker(selection: viewStore.binding(get: \.selectedPhotosPickerItems, send: EditTradeStore.Action.setPhotoPickerItems), matching: .images) { ImageNewItem() } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagStore.swift index cdf97ac..ca67f34 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagStore.swift @@ -6,6 +6,7 @@ // import Foundation +import SwiftUI import ComposableArchitecture @@ -15,29 +16,63 @@ public struct SelectTagStore: Reducer { public init() {} public struct State: Equatable { - public init() { } + public var tagItem: IdentifiedArrayOf = [] + + public var selectedTags: [Tag] + public var name: String = "" + public var color: Color = .blackOrWhite() + + public init(selectedTags: [Tag]) { + self.selectedTags = selectedTags + } } public enum Action: Equatable { case onAppear - case currencyTapped(Currency) + case setName(String) + case setColor(Color) + case dismissButtonTapped + case confirmButtonTapped + + case fetchTagsRequest + case fetchTagsResponse([Tag]) + + case tagItem(id: TagItemCellStore.State.ID, action: TagItemCellStore.Action) case delegate(Delegate) public enum Delegate: Equatable { - case select(Currency) + case select([Tag]) } } + @Dependency(\.tagClient) var tagClient + public var body: some ReducerOf { Reduce { state, action in switch action { case .onAppear: return .none - case let .currencyTapped(currency): - return .send(.delegate(.select(currency))) + case .fetchTagsRequest: + let tags = (try? tagClient.fetchTags().get()) ?? [] + return .send(.fetchTagsResponse(tags)) + + case let .fetchTagsResponse(tags): + state.tagItem = .init( + uniqueElements: tags.map { tag in + return .init( + tag: tag, + isSelected: state.selectedTags.contains(where: { $0 == tag}) + ) + } + ) + return .none + + case .confirmButtonTapped: + + return .none default: return .none diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagView.swift index d6d9aae..c77d320 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagView.swift @@ -14,41 +14,58 @@ import ToolinderDomain import ToolinderShared public struct SelectTagView: View { - let store: StoreOf + let store: StoreOf - public init(store: StoreOf) { + public init(store: StoreOf) { self.store = store } public var body: some View { WithViewStore(self.store, observe: { $0 }) { viewStore in ScrollView { - HStack { - Text("Currency") - .font(.title) - Spacer() - } - .padding() - - LazyVGrid(columns: .init(repeating: .init(.flexible(minimum: 10, maximum: 500)), count: 3), alignment: .leading, spacing: 10) { - ForEach(Currency.allCases, id: \.self) { currency in - Label(currency.rawValue, systemImage: currency.systemImageName) - .font(.body) - .padding(10) - .background(Color(uiColor: .systemGray6)) - .clipShape( - RoundedRectangle( - cornerRadius: 8, - style: .continuous - ) - ) - .onTapGesture { - viewStore.send(.delegate(.select(currency))) - } + VStack(alignment: .leading) { + EditHeaderView(mode: .bypassAdd, title: "Tag") { action in + switch action { + case .dismiss: + viewStore.send(.dismissButtonTapped) + case .delete: break + } + } + + tagItemListView() + + Divider() + + nameView(viewStore: viewStore) + + colorView(viewStore: viewStore) + + MinimalButton(title: "Confirm") { + viewStore.send(.confirmButtonTapped) } } - .padding(.horizontal) + .padding() + } + } + } + + private func tagItemListView() -> some View { + LazyVGrid(columns: .init(repeating: .init(.flexible(minimum: 10, maximum: 500)), count: 3), alignment: .leading, spacing: 10) { + ForEachStore(self.store.scope(state: \.tagItem, action: SelectTagStore.Action.tagItem(id:action:))) { + TagItemCellView(store: $0) } } + .padding(.horizontal) + } + + private func nameView(viewStore: ViewStoreOf) -> some View { + TextField("Name", text: viewStore.binding(get: \.name, send: SelectTagStore.Action.setName)) + .foregroundStyle(.foreground) + } + + private func colorView(viewStore: ViewStoreOf) -> some View { + ColorPicker(selection: viewStore.binding(get: \.color, send: SelectTagStore.Action.setColor), label: { + Label("Color", systemImage: "paintpalette.fill") + }) } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/TickerDetail/TickerDetailStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/TickerDetail/TickerDetailStore.swift index 8b6550a..c322f93 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/TickerDetail/TickerDetailStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/TickerDetail/TickerDetailStore.swift @@ -19,7 +19,7 @@ public struct TickerDetailStore: Reducer { public var tradeDateChartDataEntity: TradeDateChartDataEntity = .init() - @PresentationState var tickerEdit: TickerEditStore.State? + @PresentationState var editTicker: EditTickerStore.State? public var tradeItem: IdentifiedArrayOf = [] public init( @@ -37,7 +37,7 @@ public struct TickerDetailStore: Reducer { case tickerTypeChartDataEntityRequest case tickerTypeChartDataEntityResponse(TradeDateChartDataEntity) - case tickerEdit(PresentationAction) + case editTicker(PresentationAction) case tradeItem(id: TradeItemCellStore.State.ID, action: TradeItemCellStore.Action) case delegate(Delegate) @@ -61,7 +61,7 @@ public struct TickerDetailStore: Reducer { ]) case .editButtonTapped: - state.tickerEdit = .init(mode: .edit, selectedTicker: state.ticker) + state.editTicker = .init(mode: .edit, selectedTicker: state.ticker) return .none case .tickerTypeChartDataEntityRequest: @@ -78,12 +78,12 @@ public struct TickerDetailStore: Reducer { state.tradeDateChartDataEntity = entity return .none - case .tickerEdit(.presented(.delegate(.delete))): - state.tickerEdit = nil + case .editTicker(.presented(.delegate(.delete))): + state.editTicker = nil return .send(.delegate(.deleted)) - case .tickerEdit(.dismiss): - state.tickerEdit = nil + case .editTicker(.dismiss): + state.editTicker = nil return .none default: @@ -91,8 +91,8 @@ public struct TickerDetailStore: Reducer { } } - .ifLet(\.$tickerEdit, action: /Action.tickerEdit) { - TickerEditStore() + .ifLet(\.$editTicker, action: /Action.editTicker) { + EditTickerStore() } } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/TickerDetail/TickerDetailView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/TickerDetail/TickerDetailView.swift index b6a148d..6a99b57 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/TickerDetail/TickerDetailView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/TickerDetail/TickerDetailView.swift @@ -37,11 +37,11 @@ public struct TickerDetailView: View { } .sheet( store: self.store.scope( - state: \.$tickerEdit, - action: { .tickerEdit($0) } + state: \.$editTicker, + action: { .editTicker($0) } ) ) { - TickerEditView(store: $0) + EditTickerView(store: $0) .presentationDetents([.medium]) } .toolbar { diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/TradeDetail/TradeDetailStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/TradeDetail/TradeDetailStore.swift index a5060ae..5d08f10 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/TradeDetail/TradeDetailStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/TradeDetail/TradeDetailStore.swift @@ -17,7 +17,7 @@ public struct TradeDetailStore: Reducer { public struct State: Equatable { public var trade: Trade - @PresentationState var tradeEdit: TradeEditStore.State? + @PresentationState var editTrade: EditTradeStore.State? public var tradeItem: IdentifiedArrayOf = [] public init(trade: Trade) { @@ -31,7 +31,7 @@ public struct TradeDetailStore: Reducer { case editButtonTapped case newButtonTapped - case tradeEdit(PresentationAction) + case editTrade(PresentationAction) case tradeItem(id: TradeItemCellStore.State.ID, action: TradeItemCellStore.Action) case delegate(Delegate) @@ -56,27 +56,27 @@ public struct TradeDetailStore: Reducer { case .editButtonTapped: if let ticker = state.trade.ticker { - state.tradeEdit = .init(mode: .edit, selectedTicker: ticker, selectedTrade: state.trade) + state.editTrade = .init(mode: .edit, selectedTicker: ticker, selectedTrade: state.trade) } return .none case .newButtonTapped: if let ticker = state.trade.ticker { - state.tradeEdit = .init(mode: .bypassAdd, selectedTicker: ticker) + state.editTrade = .init(mode: .bypassAdd, selectedTicker: ticker) } return .none - case let .tradeEdit(.presented(.delegate(.save(trade)))): + case let .editTrade(.presented(.delegate(.save(trade)))): state.trade = trade - state.tradeEdit = nil + state.editTrade = nil return .none - case let .tradeEdit(.presented(.delegate(.delete(trade)))): - state.tradeEdit = nil + case let .editTrade(.presented(.delegate(.delete(trade)))): + state.editTrade = nil return .send(.delegate(.delete(trade))) - case .tradeEdit(.dismiss), .tradeEdit(.presented(.delegate(.cancel))): - state.tradeEdit = nil + case .editTrade(.dismiss), .editTrade(.presented(.delegate(.cancel))): + state.editTrade = nil return .none default: @@ -84,8 +84,8 @@ public struct TradeDetailStore: Reducer { } } - .ifLet(\.$tradeEdit, action: /Action.tradeEdit) { - TradeEditStore() + .ifLet(\.$editTrade, action: /Action.editTrade) { + EditTradeStore() } } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/TradeDetail/TradeDetailView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/TradeDetail/TradeDetailView.swift index f2c9b40..d38eaf5 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/TradeDetail/TradeDetailView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/TradeDetail/TradeDetailView.swift @@ -45,11 +45,11 @@ public struct TradeDetailView: View { } .sheet( store: self.store.scope( - state: \.$tradeEdit, - action: { .tradeEdit($0) } + state: \.$editTrade, + action: { .editTrade($0) } ) ) { - TradeEditView(store: $0) + EditTradeView(store: $0) .presentationDetents([.medium]) } .navigationTitle(viewStore.state.trade.ticker?.name ?? "") From 47dd8d7d298e9a79ec0cea1bc31de7a4ee0603fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Mon, 2 Oct 2023 13:09:29 +0900 Subject: [PATCH 07/19] =?UTF-8?q?refactor:=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EC=9C=84=EA=B3=84=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SelectTicker/SelectTickerStore.swift | 8 ++++++++ .../SelectTicker/SelectTickerView.swift | 20 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift create mode 100644 Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerView.swift diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift new file mode 100644 index 0000000..a789ff9 --- /dev/null +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift @@ -0,0 +1,8 @@ +// +// SelectTickerStore.swift +// ToolinderFeatureTradeInterface +// +// Created by 송영모 on 2023/10/02. +// + +import Foundation diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerView.swift new file mode 100644 index 0000000..a054718 --- /dev/null +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerView.swift @@ -0,0 +1,20 @@ +// +// SelectTicker.swift +// ToolinderFeatureTradeInterface +// +// Created by 송영모 on 2023/10/02. +// + +import SwiftUI + +import ComposableArchitecture + +public struct SelectTickerView: View { + public let store: StoreOf + + public var body: some View { + HStack { + + } + } +} From f11092e4e6985d1f385afd26ff6402972ddcd863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Tue, 3 Oct 2023 11:40:20 +0900 Subject: [PATCH 08/19] =?UTF-8?q?feat:=20edit=20ticker=20view=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Calendar/CalendarStore.swift | 31 ++++--- .../Sources/Calendar/CalendarView.swift | 6 +- .../Components/View/EditHeaderView.swift | 27 +++++- .../Sources/EditTag/EditTagView.swift | 1 + .../Sources/EditTicker/EditTickerStore.swift | 89 +++++-------------- .../Sources/EditTicker/EditTickerView.swift | 65 +++++++------- .../Sources/EditTicker/TickerItem.swift | 78 ---------------- .../Sources/SelectTag/SelectTagView.swift | 4 +- .../SelectTicker/SelectTickerStore.swift | 77 ++++++++++++++++ .../SelectTicker/SelectTickerView.swift | 48 +++++++++- .../TickerDetail/TickerDetailStore.swift | 2 +- 11 files changed, 221 insertions(+), 207 deletions(-) delete mode 100644 Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/TickerItem.swift diff --git a/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarStore.swift b/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarStore.swift index c3a5cd6..9036781 100644 --- a/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarStore.swift +++ b/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarStore.swift @@ -43,7 +43,7 @@ public struct CalendarStore: Reducer { public var calendarItem: IdentifiedArrayOf = [] public var tradeItem: IdentifiedArrayOf = [] - @PresentationState var editTicker: EditTickerStore.State? + @PresentationState var selectTicker: SelectTickerStore.State? @PresentationState var editTrade: EditTradeStore.State? public init( @@ -83,7 +83,7 @@ public struct CalendarStore: Reducer { case calendarItem(id: CalendarItemCellStore.State.ID, action: CalendarItemCellStore.Action) case tradeItem(id: TradeItemCellStore.State.ID, action: TradeItemCellStore.Action) - case editTicker(PresentationAction) + case selectTicker(PresentationAction) case editTrade(PresentationAction) case delegate(Delegate) @@ -104,7 +104,7 @@ public struct CalendarStore: Reducer { return .none case .newButtonTapped: - state.editTicker = .init() + state.selectTicker = .init() return .none case let .tradeItemTapped(trade): @@ -142,26 +142,25 @@ public struct CalendarStore: Reducer { return .none } - case .editTicker(.presented(.delegate(.cancel))): - state.editTicker = nil - return .none +// case let .selectTicker(.presented): +// return .none - case let .editTicker(.presented(.delegate(.next(ticker)))): - state.editTicker = nil - state.editTrade = .init(selectedTicker: ticker, selectedDate: state.selectedDate) - return .none +// case let .editTicker(.presented(.delegate(.next(ticker)))): +// state.editTicker = nil +// state.editTrade = .init(selectedTicker: ticker, selectedDate: state.selectedDate) +// return .none - case .editTicker(.dismiss): - state.editTicker = nil + case .selectTicker(.dismiss): + state.selectTicker = nil return .none case .editTrade(.presented(.delegate(.save))): - state.editTicker = nil + state.selectTicker = nil state.editTrade = nil return .none case let .editTrade(.presented(.delegate(.cancel(ticker)))): - state.editTicker = .init(selectedTicker: ticker) + state.selectTicker = .init(selectedTicker: ticker) state.editTrade = nil return .none @@ -179,8 +178,8 @@ public struct CalendarStore: Reducer { .forEach(\.tradeItem, action: /Action.tradeItem(id:action:)) { TradeItemCellStore() } - .ifLet(\.$editTicker, action: /Action.editTicker) { - EditTickerStore() + .ifLet(\.$selectTicker, action: /Action.selectTicker) { + SelectTickerStore() } .ifLet(\.$editTrade, action: /Action.editTrade) { EditTradeStore() diff --git a/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarView.swift b/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarView.swift index 172d8e5..8707c54 100644 --- a/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarView.swift +++ b/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarView.swift @@ -48,11 +48,11 @@ public struct CalendarView: View { } .sheet( store: self.store.scope( - state: \.$editTicker, - action: { .editTicker($0) } + state: \.$selectTicker, + action: { .selectTicker($0) } ) ) { - EditTickerView(store: $0) + SelectTickerView(store: $0) .presentationDetents([.medium]) } .sheet( diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/View/EditHeaderView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/View/EditHeaderView.swift index b832b52..9e9d4c2 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/View/EditHeaderView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/View/EditHeaderView.swift @@ -10,11 +10,12 @@ import SwiftUI public enum EditMode { case add - case bypassAdd case edit + case select public enum Action { case dismiss + case new case delete } } @@ -22,22 +23,32 @@ public enum EditMode { public struct EditHeaderView: View { public let mode: EditMode public let title: LocalizedStringKey + public let isShowDismissButton: Bool + public let isShowNewButton: Bool + public let isShowDeleteButton: Bool public var action: (EditMode.Action) -> () public init( mode: EditMode, title: LocalizedStringKey, + isShowDismissButton: Bool = false, + isShowNewButton: Bool = false, + isShowDeleteButton: Bool = false, action: @escaping (EditMode.Action) -> Void ) { self.mode = mode self.title = title + self.isShowDismissButton = isShowDismissButton + self.isShowNewButton = isShowNewButton + self.isShowDeleteButton = isShowDeleteButton + self.action = action } public var body: some View { HStack { - if mode == .add { + if isShowDismissButton { Button(action: { action(.dismiss) }, label: { @@ -52,7 +63,7 @@ public struct EditHeaderView: View { Spacer() - if mode == .edit { + if isShowDeleteButton { Button(action: { action(.delete) }, label: { @@ -61,6 +72,16 @@ public struct EditHeaderView: View { .font(.title) }) } + + if isShowNewButton { + Button(action: { + action(.new) + }, label: { + Image(systemName: "plus.circle.fill") + .foregroundStyle(.foreground) + .font(.title) + }) + } } } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagView.swift index 4a3104a..f2e39ca 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagView.swift @@ -30,6 +30,7 @@ public struct EditTagView: View { viewStore.send(.dismissButtonTapped) case .delete: viewStore.send(.deleteButtonTapped) + default: break } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift index 32966b1..3e2c4a6 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift @@ -14,21 +14,15 @@ import ToolinderDomainTradeInterface public struct EditTickerStore: Reducer { public init() {} - public enum Mode { - case add - case edit - } - public struct State: Equatable { - public var mode: Mode + public var mode: EditMode + public var ticker: Ticker? + public var name: String = "" public var selectedTickerType: TickerType? public var selectedCurrency: Currency? public var selectedTags: [Tag] = [] - public var selectedTicker: Ticker? - - public var tickerItem: IdentifiedArrayOf = [] public var tagItem: IdentifiedArrayOf = [] @PresentationState var selectTickerType: SelectTickerTypeStore.State? @PresentationState var selectCurrency: SelectCurrencyStore.State? @@ -36,18 +30,18 @@ public struct EditTickerStore: Reducer { @PresentationState var alert: AlertState? public init( - mode: Mode = .add, - selectedTicker: Ticker? = nil + mode: EditMode = .add, + ticker: Ticker? = nil ) { self.mode = mode - self.selectedTicker = selectedTicker + self.ticker = ticker if mode == .edit { - self.name = selectedTicker?.name ?? "" - self.selectedTickerType = selectedTicker?.type ?? .stock - self.selectedCurrency = selectedTicker?.currency ?? .dollar + self.name = ticker?.name ?? "" + self.selectedTickerType = ticker?.type ?? .stock + self.selectedCurrency = ticker?.currency ?? .dollar self.tagItem = .init( - uniqueElements: selectedTicker?.tags?.map { tag in + uniqueElements: ticker?.tags?.map { tag in return .init(tag: tag) } ?? [] ) @@ -62,13 +56,10 @@ public struct EditTickerStore: Reducer { case tickerTypeButtonTapped case currencyButtonTapped case tagButtonTapped + case dismissButtonTapped case deleteButtonTapped - case nextButtonTapped - - case fetchTickersRequest - case fetchTickersResponse([Ticker]) + case saveButtonTapped - case tickerItem(id: TickerItemCellStore.State.ID, action: TickerItemCellStore.Action) case tagItem(id: TagItemCellStore.State.ID, action: TagItemCellStore.Action) case selectTickerType(PresentationAction) case selectCurrency(PresentationAction) @@ -82,8 +73,7 @@ public struct EditTickerStore: Reducer { } public enum Delegate: Equatable { - case cancel - case next(Ticker) + case cancle case save(Ticker) case delete(Ticker) } @@ -95,9 +85,7 @@ public struct EditTickerStore: Reducer { Reduce { state, action in switch action { case .onAppear: - return .concatenate([ - .send(.fetchTickersRequest) - ]) + return .none case let .setName(name): state.name = name @@ -117,7 +105,7 @@ public struct EditTickerStore: Reducer { case .deleteButtonTapped: state.alert = AlertState { - TextState("\(state.selectedTicker?.trades?.count ?? 0) records are also deleted.") + TextState("\(state.ticker?.trades?.count ?? 0) records are also deleted.") } actions: { ButtonState(role: .destructive, action: .confirmDeletion) { TextState("Delete") @@ -125,48 +113,15 @@ public struct EditTickerStore: Reducer { } return .none - case .nextButtonTapped: - if let ticker = state.selectedTicker, state.mode == .add { - return .send(.delegate(.next(ticker))) - } - + case .saveButtonTapped: return validateAndSaveTickerEffect( mode: state.mode, - ticker: state.selectedTicker, + ticker: state.ticker, tickerType: state.selectedTickerType, currency: state.selectedCurrency, name: state.name ) - case .fetchTickersRequest: - let tickers = (try? tickerClient.fetchTickers().get()) ?? [] - return .send(.fetchTickersResponse(tickers)) - - case let .fetchTickersResponse(tickers): - state.tickerItem = .init( - uniqueElements: tickers.map { - .init(mode: .preview, ticker: $0) - } - ) - return .none - - case let .tickerItem(id: id, action: .delegate(.tapped)): - let isSelected = state.tickerItem[id: id]?.isSelected ?? false - - for id in state.tickerItem.ids { - state.tickerItem[id: id]?.isSelected = false - } - - state.tickerItem[id: id]?.isSelected = !isSelected - - if !isSelected { - state.selectedTicker = state.tickerItem[id: id]?.ticker - } else { - state.selectedTicker = nil - } - - return .none - case let .selectTickerType(.presented(.delegate(.select(tickerType)))): state.selectTickerType = nil state.selectedTickerType = tickerType @@ -190,7 +145,7 @@ public struct EditTickerStore: Reducer { return .none case .alert(.presented(.confirmDeletion)): - if let ticker = state.selectedTicker { + if let ticker = state.ticker { let _ = tickerClient.deleteTicker(ticker) return .send(.delegate(.delete(ticker))) } @@ -200,14 +155,11 @@ public struct EditTickerStore: Reducer { return .none } } - .forEach(\.tickerItem, action: /Action.tickerItem(id:action:)) { - TickerItemCellStore() - } .ifLet(\.$alert, action: /Action.alert) } private func validateAndSaveTickerEffect( - mode: Mode, + mode: EditMode, ticker: Ticker?, tickerType: TickerType?, currency: Currency?, @@ -220,7 +172,7 @@ public struct EditTickerStore: Reducer { switch mode { case .add: if let ticker = try? tickerClient.saveTicker(.init(type: tickerType, currency: currency, name: name, tags: [])).get() { - return .send(.delegate(.next(ticker))) + return .send(.delegate(.save(ticker))) } else { return .none } @@ -232,6 +184,7 @@ public struct EditTickerStore: Reducer { } else { return .none } + default: return .none } } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerView.swift index 4473a5c..bc0db13 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerView.swift @@ -24,11 +24,7 @@ public struct EditTickerView: View { WithViewStore(self.store, observe: { $0 }) { viewStore in VStack(alignment: .leading, spacing: 20) { headerView(viewStore: viewStore) - .padding([.top, .horizontal]) - - if viewStore.state.mode == .add { - tickersView(viewStore: viewStore) - } + .padding() Divider() .padding(.horizontal) @@ -47,10 +43,10 @@ public struct EditTickerView: View { Spacer() - MinimalButton(title: "Next") { - viewStore.send(.nextButtonTapped) + MinimalButton(title: "Save") { + viewStore.send(.saveButtonTapped) } - .padding(.horizontal) + .padding() } .onAppear { viewStore.send(.onAppear) @@ -91,38 +87,39 @@ public struct EditTickerView: View { } } + @ViewBuilder private func headerView(viewStore: ViewStoreOf) -> some View { - HStack { - if viewStore.state.mode == .add { - Text("Ticker") - .font(.title) - } else { - Text(viewStore.state.selectedTicker?.name ?? "") - .font(.title) + switch viewStore.state.mode { + case .add: + EditHeaderView( + mode: viewStore.state.mode, + title: LocalizedStringKey(viewStore.state.ticker?.name ?? "Ticker"), + isShowDismissButton: true, + isShowDeleteButton: false + ) { mode in + switch mode { + case .dismiss: + viewStore.send(.dismissButtonTapped) + default: break + } } - Spacer() - - if viewStore.state.mode == .edit { - Button(action: { + case .edit: + EditHeaderView( + mode: viewStore.state.mode, + title: LocalizedStringKey(viewStore.state.ticker?.name ?? "Ticker"), + isShowDismissButton: true, + isShowDeleteButton: true + ) { mode in + switch mode { + case .dismiss: + viewStore.send(.dismissButtonTapped) + case .delete: viewStore.send(.deleteButtonTapped) - }, label: { - Image(systemName: "trash.circle.fill") - .foregroundStyle(.foreground) - .font(.title) - }) - } - } - } - - private func tickersView(viewStore: ViewStoreOf) -> some View { - ScrollView(.horizontal) { - HStack { - ForEachStore(self.store.scope(state: \.tickerItem, action: EditTickerStore.Action.tickerItem(id:action:))) { - TickerItemCellView(store: $0) + default: break } } - .padding(.horizontal) + default: EmptyView() } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/TickerItem.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/TickerItem.swift deleted file mode 100644 index 6e214b8..0000000 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/TickerItem.swift +++ /dev/null @@ -1,78 +0,0 @@ -//// -//// TickerItem.swift -//// ToolinderFeatureCalendarInterface -//// -//// Created by 송영모 on 2023/09/11. -//// -// -//import SwiftUI -// -//import ToolinderDomain -// -//public struct TickerItem: View { -// private let ticker: Ticker -// private let isSelected: Bool -// -// private var action: () -> Void -// -// public init( -// ticker: Ticker, -// isSelected: Bool, -// action: @escaping () -> Void = {} -// ) { -// self.ticker = ticker -// self.isSelected = isSelected -// self.action = action -// } -// -// public var body: some View { -// VStack(spacing: .zero) { -// HStack { -// Image(systemName: ticker.type.systemImageName) -// .font(.body) -// -// Text("\(ticker.name) \(ticker.trades?.count ?? 0)") -// .font(.body) -// .fontWeight(.semibold) -// .padding(.trailing) -// -// Spacer() -// -// Image(systemName: "checkmark.circle") -// .font(.caption) -// } -// .padding(.bottom, 10) -// -// HStack(spacing: .zero) { -// Spacer() -// -// Text("++76 ") -// .font(.caption2) -// .foregroundStyle(.pink) -// Text("--59") -// .font(.caption2) -// .foregroundStyle(.mint) -// Text(" 12 vol") -// .font(.caption2) -// } -// -// HStack(spacing: .zero) { -// Spacer() -// -// Text("(avg) 12,000 \(ticker.currency.rawValue)") -// .font(.caption2) -// } -// } -// .padding(10) -// .background(isSelected ? Color(uiColor: .systemGray5) : Color(uiColor: .systemGray6)) -// .clipShape( -// RoundedRectangle( -// cornerRadius: 8, -// style: .continuous -// ) -// ) -// .onTapGesture { -// self.action() -// } -// } -//} diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagView.swift index c77d320..970f8b1 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagView.swift @@ -24,11 +24,11 @@ public struct SelectTagView: View { WithViewStore(self.store, observe: { $0 }) { viewStore in ScrollView { VStack(alignment: .leading) { - EditHeaderView(mode: .bypassAdd, title: "Tag") { action in + EditHeaderView(mode: .select, title: "Tag") { action in switch action { case .dismiss: viewStore.send(.dismissButtonTapped) - case .delete: break + default: break } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift index a789ff9..6b7a857 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift @@ -6,3 +6,80 @@ // import Foundation +import SwiftUI + +import ComposableArchitecture + +import ToolinderDomain + +public struct SelectTickerStore: Reducer { + public init() {} + + public struct State: Equatable { + public var tickerItem: IdentifiedArrayOf = [] + + public var selectedTicker: Ticker? + + @PresentationState var editTicker: EditTickerStore.State? + + public init(selectedTicker: Ticker? = nil) { + self.selectedTicker = selectedTicker + } + } + + public enum Action: Equatable { + case onAppear + + case addButtonTapped + + case fetchTickersRequest + case fetchTickersResponse([Ticker]) + + case tickerItem(id: TickerItemCellStore.State.ID, action: TickerItemCellStore.Action) + case editTicker(PresentationAction) + + case delegate(Delegate) + + public enum Delegate: Equatable { + case select(Ticker) + } + } + + @Dependency(\.tickerClient) var tickerClient + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case .onAppear: + return .send(.fetchTickersRequest) + + case .addButtonTapped: + state.editTicker = .init() + return .none + + case .fetchTickersRequest: + let tags = (try? tickerClient.fetchTickers().get()) ?? [] + return .send(.fetchTickersResponse(tags)) + + case let .fetchTickersResponse(tickers): + state.tickerItem = .init( + uniqueElements: tickers.map { ticker in + .init( + mode: .preview, + ticker: ticker, + isSelected: state.selectedTicker == ticker + ) + } + ) + return .none + + default: + return .none + } + } + + .ifLet(\.$editTicker, action: /Action.editTicker) { + EditTickerStore() + } + } +} diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerView.swift index a054718..13671e9 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerView.swift @@ -9,12 +9,56 @@ import SwiftUI import ComposableArchitecture +import ToolinderShared + public struct SelectTickerView: View { - public let store: StoreOf + public let store: StoreOf + + public init(store: StoreOf) { + self.store = store + } public var body: some View { + WithViewStore(self.store, observe: { $0 }) { viewStore in + ScrollView { + VStack { + EditHeaderView( + mode: .select, + title: "Ticker", + isShowNewButton: true + ) { _ in } + .padding() + + if viewStore.tickerItem.isEmpty { + tickerItemListEmptyView(viewStore: viewStore) + .padding() + } else { + tickerItemListView() + .padding() + } + } + } + } + } + + private func tickerItemListView() -> some View { + LazyVGrid(columns: .init(repeating: .init(.flexible(minimum: 10, maximum: 500)), count: 3), alignment: .leading, spacing: 10) { + ForEachStore(self.store.scope(state: \.tickerItem, action: SelectTickerStore.Action.tickerItem(id:action:))) { + TickerItemCellView(store: $0) + } + } + .padding(.horizontal) + } + + private func tickerItemListEmptyView(viewStore: ViewStoreOf) -> some View { HStack { - + Button(action: { + viewStore.send(.addButtonTapped) + }, label: { + Image(systemName: "cloud.rain.fill") + .font(.largeTitle) + .foregroundStyle(.foreground) + }) } } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/TickerDetail/TickerDetailStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/TickerDetail/TickerDetailStore.swift index c322f93..b7e8039 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/TickerDetail/TickerDetailStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/TickerDetail/TickerDetailStore.swift @@ -61,7 +61,7 @@ public struct TickerDetailStore: Reducer { ]) case .editButtonTapped: - state.editTicker = .init(mode: .edit, selectedTicker: state.ticker) + state.editTicker = .init(mode: .edit, ticker: state.ticker) return .none case .tickerTypeChartDataEntityRequest: From 00023361cd9fdec600a6e6bd7d51863ab6385500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Tue, 3 Oct 2023 12:55:17 +0900 Subject: [PATCH 09/19] =?UTF-8?q?feat:=20tag=20edit=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Cell/TagItemCellView.swift | 19 +++-- .../Sources/EditTag/EditTagStore.swift | 10 ++- .../Sources/EditTag/EditTagView.swift | 70 +++++++------------ .../Sources/EditTicker/EditTickerStore.swift | 22 +++++- .../Sources/EditTicker/EditTickerView.swift | 12 +--- .../Sources/SelectTag/SelectTagStore.swift | 48 ++++++++++--- .../Sources/SelectTag/SelectTagView.swift | 68 ++++++++++-------- .../SelectTicker/SelectTickerStore.swift | 7 ++ .../SelectTicker/SelectTickerView.swift | 60 ++++++++-------- 9 files changed, 181 insertions(+), 135 deletions(-) diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellView.swift index 1fa18dd..459b400 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellView.swift @@ -21,29 +21,28 @@ public struct TagItemCellView: View { public var body: some View { WithViewStore(self.store, observe: { $0 }) { viewStore in - HStack(spacing: 2) { + HStack(spacing: 10) { Circle() .fill(Color(hex: viewStore.state.tag.hex)) + .frame(width: 20, height: 20) -// RoundedRectangle(cornerRadius: 3) -// .fill(viewStore.state.trade.side == .buy ? .pink : .mint) -// .frame(width: 2.5, height: 11) - -// Text(viewStore.state.trade.ticker?.name ?? "") -// .font(.caption2) -// .fontWeight(.light) -// .foregroundStyle(Color.blackOrWhite(!viewStore.state.isSelected)) + Text(viewStore.state.tag.name) + .font(.caption2) + .foregroundStyle(.foreground) Spacer() } .padding(10) - .background(Color(uiColor: .systemGray6)) + .background(viewStore.state.isSelected ? Color(uiColor: .systemGray5) : Color(uiColor: .systemGray6)) .clipShape( RoundedRectangle( cornerRadius: 8, style: .continuous ) ) + .onTapGesture { + viewStore.send(.tapped) + } } } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagStore.swift index 083c270..55445a0 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagStore.swift @@ -40,12 +40,12 @@ public struct EditTagStore: Reducer { case setTagColor(Color) case dismissButtonTapped case deleteButtonTapped - case confirmButtonTapped + case saveButtonTapped case delegate(Delegate) public enum Delegate: Equatable { - case confirm + case save(Tag) } } @@ -65,7 +65,11 @@ public struct EditTagStore: Reducer { state.tagColor = color return .none - case .confirmButtonTapped: + case .saveButtonTapped: + guard state.tagName != "" else { return .none } + if let tag = try? tagClient.saveTag(.init(hex: state.tagColor.toHex(), name: state.tagName)).get() { + return .send(.delegate(.save(tag))) + } return .none default: diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagView.swift index f2e39ca..a1a8afe 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagView.swift @@ -22,60 +22,44 @@ public struct EditTagView: View { public var body: some View { WithViewStore(self.store, observe: { $0 }) { viewStore in - ScrollView { - VStack(alignment: .leading) { - EditHeaderView(mode: viewStore.state.mode, title: "") { action in - switch action { - case .dismiss: - viewStore.send(.dismissButtonTapped) - case .delete: - viewStore.send(.deleteButtonTapped) - default: break - } - } - - nameView(viewStore: viewStore) - - colorView(viewStore: viewStore) - - MinimalButton(title: "Confirm") { - viewStore.send(.confirmButtonTapped) - } + VStack(alignment: .leading, spacing: 20) { + headerView(viewStore: viewStore) + + nameView(viewStore: viewStore) + + colorView(viewStore: viewStore) + + Spacer() + + MinimalButton(title: "Save") { + viewStore.send(.saveButtonTapped) } - .padding() } + .padding() } } + @ViewBuilder private func headerView(viewStore: ViewStoreOf) -> some View { - HStack { - if viewStore.state.mode == .add { - Button(action: { - viewStore.send(.dismissButtonTapped) - }, label: { - Image(systemName: "chevron.left") - .font(.title) - .foregroundStyle(.foreground) - }) - } - - Spacer() - - if viewStore.state.mode == .edit { - Button(action: { - viewStore.send(.deleteButtonTapped) - }, label: { - Image(systemName: "trash.circle.fill") - .foregroundStyle(.foreground) - .font(.title) - }) + EditHeaderView(mode: viewStore.state.mode, title: "Tag") { action in + switch action { + case .dismiss: + viewStore.send(.dismissButtonTapped) + case .delete: + viewStore.send(.deleteButtonTapped) + default: break } } } private func nameView(viewStore: ViewStoreOf) -> some View { - TextField("Name", text: viewStore.binding(get: \.tagName, send: EditTagStore.Action.setTagName)) - .foregroundStyle(.foreground) + TextField( + text: viewStore.binding(get: \.tagName, send: EditTagStore.Action.setTagName), + label: { + Label("Name", systemImage: "highlighter") + } + ) + .foregroundStyle(.foreground) } private func colorView(viewStore: ViewStoreOf) -> some View { diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift index 3e2c4a6..06a68b6 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift @@ -21,7 +21,15 @@ public struct EditTickerStore: Reducer { public var name: String = "" public var selectedTickerType: TickerType? public var selectedCurrency: Currency? - public var selectedTags: [Tag] = [] + public var selectedTags: [Tag] = [] { + didSet { + tagItem = .init( + uniqueElements: selectedTags.map { tag in + return .init(tag: tag) + } + ) + } + } public var tagItem: IdentifiedArrayOf = [] @PresentationState var selectTickerType: SelectTickerTypeStore.State? @@ -103,6 +111,9 @@ public struct EditTickerStore: Reducer { state.selectTag = .init(selectedTags: state.selectedTags) return .none + case .dismissButtonTapped: + return .send(.delegate(.cancle)) + case .deleteButtonTapped: state.alert = AlertState { TextState("\(state.ticker?.trades?.count ?? 0) records are also deleted.") @@ -144,6 +155,11 @@ public struct EditTickerStore: Reducer { state.selectTag = nil return .none + case let .selectTag(.presented(.delegate(.select(tags)))): + state.selectTag = nil + state.selectedTags = tags + return .none + case .alert(.presented(.confirmDeletion)): if let ticker = state.ticker { let _ = tickerClient.deleteTicker(ticker) @@ -155,6 +171,10 @@ public struct EditTickerStore: Reducer { return .none } } + .ifLet(\.$selectTag, action: /Action.selectTag) { + SelectTagStore() + } + .ifLet(\.$alert, action: /Action.alert) } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerView.swift index bc0db13..b294099 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerView.swift @@ -24,29 +24,20 @@ public struct EditTickerView: View { WithViewStore(self.store, observe: { $0 }) { viewStore in VStack(alignment: .leading, spacing: 20) { headerView(viewStore: viewStore) - .padding() - - Divider() - .padding(.horizontal) nameView(viewStore: viewStore) - .padding(.horizontal) tickerTypeView(viewStore: viewStore) - .padding(.horizontal) currencyView(viewStore: viewStore) - .padding(.horizontal) tagView(viewStore: viewStore) - .padding(.horizontal) Spacer() MinimalButton(title: "Save") { viewStore.send(.saveButtonTapped) } - .padding() } .onAppear { viewStore.send(.onAppear) @@ -84,6 +75,7 @@ public struct EditTickerView: View { action: { .alert($0) } ) ) + .padding() } } @@ -152,7 +144,7 @@ public struct EditTickerView: View { Button(action: { viewStore.send(.tagButtonTapped) }, label: { - Label("Tag", systemImage: "tag.circle.fill") + Label(viewStore.selectedTags.isEmpty ? "Tag": "", systemImage: "tag.circle.fill") .foregroundStyle(.foreground) }) diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagStore.swift index ca67f34..6278d1b 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagStore.swift @@ -16,11 +16,10 @@ public struct SelectTagStore: Reducer { public init() {} public struct State: Equatable { - public var tagItem: IdentifiedArrayOf = [] - public var selectedTags: [Tag] - public var name: String = "" - public var color: Color = .blackOrWhite() + + public var tagItem: IdentifiedArrayOf = [] + @PresentationState var editTag: EditTagStore.State? public init(selectedTags: [Tag]) { self.selectedTags = selectedTags @@ -30,15 +29,14 @@ public struct SelectTagStore: Reducer { public enum Action: Equatable { case onAppear - case setName(String) - case setColor(Color) - case dismissButtonTapped + case addButtonTapped case confirmButtonTapped case fetchTagsRequest case fetchTagsResponse([Tag]) case tagItem(id: TagItemCellStore.State.ID, action: TagItemCellStore.Action) + case editTag(PresentationAction) case delegate(Delegate) @@ -53,8 +51,17 @@ public struct SelectTagStore: Reducer { Reduce { state, action in switch action { case .onAppear: + return .concatenate([ + .send(.fetchTagsRequest) + ]) + + case .addButtonTapped: + state.editTag = .init(mode: .add) return .none + case .confirmButtonTapped: + return .send(.delegate(.select(state.selectedTags))) + case .fetchTagsRequest: let tags = (try? tagClient.fetchTags().get()) ?? [] return .send(.fetchTagsResponse(tags)) @@ -64,19 +71,42 @@ public struct SelectTagStore: Reducer { uniqueElements: tags.map { tag in return .init( tag: tag, - isSelected: state.selectedTags.contains(where: { $0 == tag}) + isSelected: state.selectedTags.contains(where: { $0 == tag }) ) } ) return .none - case .confirmButtonTapped: + case let .tagItem(id: id, action: .delegate(.tapped)): + guard let tag = state.tagItem[id: id]?.tag else { return .none } + state.tagItem[id: id]?.isSelected.toggle() + if let index = state.selectedTags.firstIndex(of: tag) { + state.selectedTags.remove(at: index) + } else { + state.selectedTags.append(tag) + } + return .none + + case .editTag(.presented(.delegate(.save))): + state.editTag = nil + return .send(.fetchTagsRequest) + + case .editTag(.dismiss): + state.editTag = nil return .none default: return .none } } + + .ifLet(\.$editTag, action: /Action.editTag) { + EditTagStore() + } + + .forEach(\.tagItem, action: /Action.tagItem(id:action:)) { + TagItemCellStore() + } } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagView.swift index 970f8b1..5ea05d8 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagView.swift @@ -22,29 +22,46 @@ public struct SelectTagView: View { public var body: some View { WithViewStore(self.store, observe: { $0 }) { viewStore in - ScrollView { - VStack(alignment: .leading) { - EditHeaderView(mode: .select, title: "Tag") { action in - switch action { - case .dismiss: - viewStore.send(.dismissButtonTapped) - default: break + GeometryReader { proxy in + ScrollView { + VStack(alignment: .leading) { + headerView(viewStore: viewStore) + .padding() + + tagItemListView() + + Spacer() + + MinimalButton(title: "Confirm") { + viewStore.send(.confirmButtonTapped) } + .padding() } - - tagItemListView() - - Divider() - - nameView(viewStore: viewStore) - - colorView(viewStore: viewStore) - - MinimalButton(title: "Confirm") { - viewStore.send(.confirmButtonTapped) - } + .frame(minHeight: proxy.size.height) } - .padding() + } + .onAppear { + viewStore.send(.onAppear) + } + .sheet( + store: self.store.scope( + state: \.$editTag, + action: { .editTag($0) } + ) + ) { + EditTagView(store: $0) + .presentationDetents([.medium]) + } + } + } + + @ViewBuilder + private func headerView(viewStore: ViewStoreOf) -> some View { + EditHeaderView(mode: .select, title: "Tag", isShowNewButton: true) { mode in + switch mode { + case .new: + viewStore.send(.addButtonTapped) + default: break } } } @@ -57,15 +74,4 @@ public struct SelectTagView: View { } .padding(.horizontal) } - - private func nameView(viewStore: ViewStoreOf) -> some View { - TextField("Name", text: viewStore.binding(get: \.name, send: SelectTagStore.Action.setName)) - .foregroundStyle(.foreground) - } - - private func colorView(viewStore: ViewStoreOf) -> some View { - ColorPicker(selection: viewStore.binding(get: \.color, send: SelectTagStore.Action.setColor), label: { - Label("Color", systemImage: "paintpalette.fill") - }) - } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift index 6b7a857..222ee67 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift @@ -73,6 +73,13 @@ public struct SelectTickerStore: Reducer { ) return .none + case .editTicker(.presented(.delegate(.save))): + return .send(.fetchTickersRequest) + + case .editTicker(.dismiss), .editTicker(.presented(.delegate(.cancle))): + state.editTicker = nil + return .none + default: return .none } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerView.swift index 13671e9..9e277c6 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerView.swift @@ -21,22 +21,38 @@ public struct SelectTickerView: View { public var body: some View { WithViewStore(self.store, observe: { $0 }) { viewStore in ScrollView { - VStack { - EditHeaderView( - mode: .select, - title: "Ticker", - isShowNewButton: true - ) { _ in } - .padding() - - if viewStore.tickerItem.isEmpty { - tickerItemListEmptyView(viewStore: viewStore) - .padding() - } else { - tickerItemListView() - .padding() - } - } + headerView(viewStore: viewStore) + .padding() + + tickerItemListView() + .padding() + } + .onAppear { + viewStore.send(.onAppear) + } + .sheet( + store: self.store.scope( + state: \.$editTicker, + action: { .editTicker($0) } + ) + ) { + EditTickerView(store: $0) + .presentationDetents([.medium]) + } + } + } + + @ViewBuilder + private func headerView(viewStore: ViewStoreOf) -> some View { + EditHeaderView( + mode: .select, + title: "Ticker", + isShowNewButton: true + ) { mode in + switch mode { + case .new: + viewStore.send(.addButtonTapped) + default: break } } } @@ -49,16 +65,4 @@ public struct SelectTickerView: View { } .padding(.horizontal) } - - private func tickerItemListEmptyView(viewStore: ViewStoreOf) -> some View { - HStack { - Button(action: { - viewStore.send(.addButtonTapped) - }, label: { - Image(systemName: "cloud.rain.fill") - .font(.largeTitle) - .foregroundStyle(.foreground) - }) - } - } } From 0dbc06d4e09113e6bead4921c197c2638797c178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Tue, 3 Oct 2023 13:02:48 +0900 Subject: [PATCH 10/19] =?UTF-8?q?feat:=20ticker=20=EC=84=A0=ED=83=9D=20?= =?UTF-8?q?=ED=9B=84=20trade=20=EC=9E=85=EB=A0=A5=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interface/Sources/Calendar/CalendarStore.swift | 11 ++++------- .../Sources/SelectTicker/SelectTickerStore.swift | 11 ++++++++++- .../Sources/SelectTicker/SelectTickerView.swift | 3 +-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarStore.swift b/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarStore.swift index 9036781..ca8c466 100644 --- a/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarStore.swift +++ b/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/CalendarStore.swift @@ -142,13 +142,10 @@ public struct CalendarStore: Reducer { return .none } -// case let .selectTicker(.presented): -// return .none - -// case let .editTicker(.presented(.delegate(.next(ticker)))): -// state.editTicker = nil -// state.editTrade = .init(selectedTicker: ticker, selectedDate: state.selectedDate) -// return .none + case let .selectTicker(.presented(.delegate(.select(ticker)))): + state.selectTicker = nil + state.editTrade = .init(selectedTicker: ticker, selectedDate: state.selectedDate) + return .none case .selectTicker(.dismiss): state.selectTicker = nil diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift index 222ee67..0a8fe31 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift @@ -73,6 +73,13 @@ public struct SelectTickerStore: Reducer { ) return .none + case let .tickerItem(id: id, action: .delegate(.tapped)): + if let ticker = state.tickerItem[id: id]?.ticker { + return .send(.delegate(.select(ticker))) + } else { + return .none + } + case .editTicker(.presented(.delegate(.save))): return .send(.fetchTickersRequest) @@ -84,9 +91,11 @@ public struct SelectTickerStore: Reducer { return .none } } - .ifLet(\.$editTicker, action: /Action.editTicker) { EditTickerStore() } + .forEach(\.tickerItem, action: /Action.tickerItem(id:action:)) { + TickerItemCellStore() + } } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerView.swift index 9e277c6..18e8439 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerView.swift @@ -58,11 +58,10 @@ public struct SelectTickerView: View { } private func tickerItemListView() -> some View { - LazyVGrid(columns: .init(repeating: .init(.flexible(minimum: 10, maximum: 500)), count: 3), alignment: .leading, spacing: 10) { + LazyVGrid(columns: .init(repeating: .init(.flexible(minimum: 10, maximum: 500)), count: 2), alignment: .leading, spacing: 10) { ForEachStore(self.store.scope(state: \.tickerItem, action: SelectTickerStore.Action.tickerItem(id:action:))) { TickerItemCellView(store: $0) } } - .padding(.horizontal) } } From be2ce902c93a561c742442b52037fbdb9f49cf69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Tue, 3 Oct 2023 13:36:56 +0900 Subject: [PATCH 11/19] =?UTF-8?q?feat:=20tag=20circle=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interface/Sources/DTO/TickerDTO.swift | 6 +- .../Components/Cell/TickerItemCellView.swift | 113 ++++++++++-------- .../Sources/EditTicker/EditTickerStore.swift | 14 ++- .../SelectTicker/SelectTickerStore.swift | 1 + .../TickerDetail/TickerDetailStore.swift | 7 +- 5 files changed, 82 insertions(+), 59 deletions(-) diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TickerDTO.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TickerDTO.swift index 63f211b..1f7b2fd 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TickerDTO.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/DTO/TickerDTO.swift @@ -13,14 +13,14 @@ public class TickerDTO { public var type: TickerType public var currency: Currency public var name: String - public var tags: [TagDTO] + public var tags: [Tag] public init( id: UUID = .init(), type: TickerType, currency: Currency, name: String, - tags: [TagDTO] + tags: [Tag] ) { self.id = id self.type = type @@ -35,7 +35,7 @@ public class TickerDTO { type: type, currency: currency, name: name, - tags: tags.map { $0.toDomain() } + tags: tags ) } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TickerItemCellView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TickerItemCellView.swift index c800732..4ed1f95 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TickerItemCellView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TickerItemCellView.swift @@ -21,68 +21,83 @@ public struct TickerItemCellView: View { public var body: some View { WithViewStore(self.store, observe: { $0 }) { viewStore in - HStack { - tradeView(viewStore: viewStore) - } - } - } - - private func tradeView(viewStore: ViewStoreOf) -> some View { - HStack(spacing: 10) { - VStack { - viewStore.state.ticker.type.image - .font(.title3) + HStack(spacing: 10) { + symbolView(viewStore: viewStore) + + nameView(viewStore: viewStore) if viewStore.mode == .item { - Text("\(viewStore.state.ticker.type.rawValue)") - .font(.caption2) - .frame(width: 40) + Spacer() + + tradeSummaryView(viewStore: viewStore) } } + .onTapGesture { + viewStore.send(.tapped) + } + .frame(height: 35) + .padding(10) + .background(viewStore.state.isSelected ? Color(uiColor: .systemGray5) : Color(uiColor: .systemGray6)) + .clipShape( + RoundedRectangle( + cornerRadius: 8 + ) + ) + } + } + + private func symbolView(viewStore: ViewStoreOf) -> some View { + VStack { + viewStore.state.ticker.type.image + .font(.title3) + if viewStore.mode == .item { + Text("\(viewStore.state.ticker.type.rawValue)") + .font(.caption2) + .frame(width: 40) + } + } + } + + private func nameView(viewStore: ViewStoreOf) -> some View { + HStack { Text("\(viewStore.state.ticker.name) \(viewStore.state.ticker.trades?.count ?? 0)" ) .font(.body) .fontWeight(.semibold) - if viewStore.mode == .item { + HStack(spacing: 1) { + ForEach(viewStore.state.ticker.tags ?? [], id: \.self) { tag in + Circle() + .fill(Color(hex: tag.hex)) + .frame(width: 5, height: 5) + } + } + } + } + + private func tradeSummaryView(viewStore: ViewStoreOf) -> some View { + VStack { + HStack { + Spacer() + Text("\(Int(viewStore.tickerSummaryDataEntity.profit)) \(viewStore.state.ticker.currency.rawValue) (\(Int(viewStore.tickerSummaryDataEntity.yield))%)") + .font(.caption) + .fontWeight(.semibold) + .foregroundStyle(Int(viewStore.tickerSummaryDataEntity.yield) > 0 ? .pink : .mint) + } + + HStack(spacing: .zero) { Spacer() - VStack { - HStack { - Spacer() - - Text("\(Int(viewStore.tickerSummaryDataEntity.profit)) \(viewStore.state.ticker.currency.rawValue) (\(Int(viewStore.tickerSummaryDataEntity.yield))%)") - .font(.caption) - .fontWeight(.semibold) - .foregroundStyle(Int(viewStore.tickerSummaryDataEntity.yield) > 0 ? .pink : .mint) - } - - HStack(spacing: .zero) { - Spacer() - - Text("\(Int(viewStore.tickerSummaryDataEntity.avgPrice)) \(viewStore.state.ticker.currency.rawValue)") - .font(.caption2) - } - - HStack(spacing: .zero) { - Spacer() + Text("\(Int(viewStore.tickerSummaryDataEntity.avgPrice)) \(viewStore.state.ticker.currency.rawValue)") + .font(.caption2) + } + + HStack(spacing: .zero) { + Spacer() - Text("\(Int(viewStore.tickerSummaryDataEntity.currentVolume)) vol") - .font(.caption2) - } - } + Text("\(Int(viewStore.tickerSummaryDataEntity.currentVolume)) vol") + .font(.caption2) } } - .frame(height: 35) - .padding(10) - .background(viewStore.state.isSelected ? Color(uiColor: .systemGray5) : Color(uiColor: .systemGray6)) - .clipShape( - RoundedRectangle( - cornerRadius: 8 - ) - ) - .onTapGesture { - viewStore.send(.tapped) - } } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift index 06a68b6..f630ba9 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift @@ -83,7 +83,7 @@ public struct EditTickerStore: Reducer { public enum Delegate: Equatable { case cancle case save(Ticker) - case delete(Ticker) + case delete } } @@ -130,7 +130,8 @@ public struct EditTickerStore: Reducer { ticker: state.ticker, tickerType: state.selectedTickerType, currency: state.selectedCurrency, - name: state.name + name: state.name, + tags: state.selectedTags ) case let .selectTickerType(.presented(.delegate(.select(tickerType)))): @@ -163,7 +164,7 @@ public struct EditTickerStore: Reducer { case .alert(.presented(.confirmDeletion)): if let ticker = state.ticker { let _ = tickerClient.deleteTicker(ticker) - return .send(.delegate(.delete(ticker))) + return .send(.delegate(.delete)) } return .none @@ -183,7 +184,8 @@ public struct EditTickerStore: Reducer { ticker: Ticker?, tickerType: TickerType?, currency: Currency?, - name: String + name: String, + tags: [Tag] ) -> Effect { guard let tickerType = tickerType else { return .none } guard let currency = currency else { return .none } @@ -191,7 +193,7 @@ public struct EditTickerStore: Reducer { switch mode { case .add: - if let ticker = try? tickerClient.saveTicker(.init(type: tickerType, currency: currency, name: name, tags: [])).get() { + if let ticker = try? tickerClient.saveTicker(.init(type: tickerType, currency: currency, name: name, tags: tags)).get() { return .send(.delegate(.save(ticker))) } else { return .none @@ -199,7 +201,7 @@ public struct EditTickerStore: Reducer { case .edit: guard let ticker = ticker else { return .none } - if let ticker = try? tickerClient.updateTicker(ticker, .init(type: tickerType, currency: currency, name: name, tags: [])).get() { + if let ticker = try? tickerClient.updateTicker(ticker, .init(type: tickerType, currency: currency, name: name, tags: tags)).get() { return .send(.delegate(.save(ticker))) } else { return .none diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift index 0a8fe31..dcec6e2 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTicker/SelectTickerStore.swift @@ -81,6 +81,7 @@ public struct SelectTickerStore: Reducer { } case .editTicker(.presented(.delegate(.save))): + state.editTicker = nil return .send(.fetchTickersRequest) case .editTicker(.dismiss), .editTicker(.presented(.delegate(.cancle))): diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/TickerDetail/TickerDetailStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/TickerDetail/TickerDetailStore.swift index b7e8039..2201ba1 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/TickerDetail/TickerDetailStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/TickerDetail/TickerDetailStore.swift @@ -15,7 +15,7 @@ public struct TickerDetailStore: Reducer { public init() {} public struct State: Equatable { - public let ticker: Ticker + public var ticker: Ticker public var tradeDateChartDataEntity: TradeDateChartDataEntity = .init() @@ -78,6 +78,11 @@ public struct TickerDetailStore: Reducer { state.tradeDateChartDataEntity = entity return .none + case let .editTicker(.presented(.delegate(.save(ticker)))): + state.editTicker = nil + state.ticker = ticker + return .none + case .editTicker(.presented(.delegate(.delete))): state.editTicker = nil return .send(.delegate(.deleted)) From 48029fd1973e831444f9a0c975ba8cd71aeef453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Tue, 3 Oct 2023 13:46:53 +0900 Subject: [PATCH 12/19] =?UTF-8?q?feat:=20update=20logic=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Trade/Interface/Sources/Repository/TickerRepository.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TickerRepository.swift b/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TickerRepository.swift index 3471bdf..d29209a 100644 --- a/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TickerRepository.swift +++ b/Projects/Toolinder/Domain/Trade/Interface/Sources/Repository/TickerRepository.swift @@ -46,6 +46,7 @@ public class TickerRepository: TickerRepositoryInterface { ticker.type = newTicker.type ticker.currency = newTicker.currency ticker.name = newTicker.name + ticker.tags = newTicker.tags return .success(ticker) } else { return .failure(.unknown) From 0dd1b65b354c71ffdd19cc9aba252dc09b317f9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Tue, 3 Oct 2023 14:48:20 +0900 Subject: [PATCH 13/19] =?UTF-8?q?feat:=20tag=20=EC=82=AD=EC=A0=9C=EC=8B=9C?= =?UTF-8?q?=20ticker=EC=97=90=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Cell/TagItemCellStore.swift | 8 +++++ .../Components/Cell/TagItemCellView.swift | 9 ++++- .../Sources/EditTag/EditTagStore.swift | 16 +++++++++ .../Sources/EditTag/EditTagView.swift | 35 +++++++++++++++---- .../Sources/EditTicker/EditTickerStore.swift | 8 ++++- .../Sources/SelectTag/SelectTagStore.swift | 17 +++++++++ 6 files changed, 84 insertions(+), 9 deletions(-) diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellStore.swift index 02e6c11..f232bd7 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellStore.swift @@ -15,16 +15,19 @@ public struct TagItemCellStore: Reducer { public init() {} public struct State: Equatable, Identifiable { + public let mode: EditMode public let id: UUID public let tag: Tag public var isSelected: Bool public init( + mode: EditMode = .edit, id: UUID = .init(), tag: Tag, isSelected: Bool = false ) { + self.mode = mode self.id = id self.tag = tag self.isSelected = isSelected @@ -35,11 +38,13 @@ public struct TagItemCellStore: Reducer { case onAppear case tapped + case editButtonTapped case delegate(Delegate) public enum Delegate: Equatable { case tapped + case editButtonTapped } } @@ -52,6 +57,9 @@ public struct TagItemCellStore: Reducer { case .tapped: return .send(.delegate(.tapped)) + case .editButtonTapped: + return .send(.delegate(.editButtonTapped)) + default: return .none } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellView.swift index 459b400..490f249 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellView.swift @@ -30,7 +30,14 @@ public struct TagItemCellView: View { .font(.caption2) .foregroundStyle(.foreground) - Spacer() + if viewStore.state.mode == .edit { + Button(action: { + viewStore.send(.editButtonTapped) + }, label: { + Image(systemName: "pencil.circle.fill") + }) + .foregroundStyle(.foreground) + } } .padding(10) .background(viewStore.state.isSelected ? Color(uiColor: .systemGray5) : Color(uiColor: .systemGray6)) diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagStore.swift index 55445a0..3c4709e 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagStore.swift @@ -30,6 +30,11 @@ public struct EditTagStore: Reducer { ) { self.mode = mode self.tag = tag + + if mode == .edit { + self.tagName = tag?.name ?? "" + self.tagColor = Color(hex: tag?.hex ?? "") + } } } @@ -45,7 +50,9 @@ public struct EditTagStore: Reducer { case delegate(Delegate) public enum Delegate: Equatable { + case cancle case save(Tag) + case delete(Tag) } } @@ -65,6 +72,15 @@ public struct EditTagStore: Reducer { state.tagColor = color return .none + case .dismissButtonTapped: + return .send(.delegate(.cancle)) + + case .deleteButtonTapped: + if let tag = state.tag, let deletedTag = try? tagClient.deleteTag(tag).get() { + return .send(.delegate(.delete(deletedTag))) + } + return .none + case .saveButtonTapped: guard state.tagName != "" else { return .none } if let tag = try? tagClient.saveTag(.init(hex: state.tagColor.toHex(), name: state.tagName)).get() { diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagView.swift index a1a8afe..69093e2 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagView.swift @@ -41,14 +41,35 @@ public struct EditTagView: View { @ViewBuilder private func headerView(viewStore: ViewStoreOf) -> some View { - EditHeaderView(mode: viewStore.state.mode, title: "Tag") { action in - switch action { - case .dismiss: - viewStore.send(.dismissButtonTapped) - case .delete: - viewStore.send(.deleteButtonTapped) - default: break + switch viewStore.state.mode { + case .add: + EditHeaderView( + mode: viewStore.state.mode, + title: "Tag", + isShowDismissButton: true + ) { action in + switch action { + case .dismiss: + viewStore.send(.dismissButtonTapped) + default: break + } + } + case .edit: + EditHeaderView( + mode: viewStore.state.mode, + title: "Tag", + isShowDeleteButton: true + ) { action in + switch action { + case .dismiss: + viewStore.send(.dismissButtonTapped) + case .delete: + viewStore.send(.deleteButtonTapped) + default: break + } } + + default: EmptyView() } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift index f630ba9..e814a8c 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift @@ -50,7 +50,7 @@ public struct EditTickerStore: Reducer { self.selectedCurrency = ticker?.currency ?? .dollar self.tagItem = .init( uniqueElements: ticker?.tags?.map { tag in - return .init(tag: tag) + return .init(mode: .select, tag: tag) } ?? [] ) } @@ -161,6 +161,12 @@ public struct EditTickerStore: Reducer { state.selectedTags = tags return .none + case let .selectTag(.presented(.delegate(.deleted(tag)))): + if let index = state.selectedTags.firstIndex(of: tag) { + state.selectedTags.remove(at: index) + } + return .none + case .alert(.presented(.confirmDeletion)): if let ticker = state.ticker { let _ = tickerClient.deleteTicker(ticker) diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagStore.swift index 6278d1b..bd744ed 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/SelectTag/SelectTagStore.swift @@ -42,6 +42,7 @@ public struct SelectTagStore: Reducer { public enum Delegate: Equatable { case select([Tag]) + case deleted(Tag) } } @@ -70,11 +71,13 @@ public struct SelectTagStore: Reducer { state.tagItem = .init( uniqueElements: tags.map { tag in return .init( + mode: .edit, tag: tag, isSelected: state.selectedTags.contains(where: { $0 == tag }) ) } ) + return .none case let .tagItem(id: id, action: .delegate(.tapped)): @@ -88,10 +91,24 @@ public struct SelectTagStore: Reducer { } return .none + case let .tagItem(id: id, action: .delegate(.editButtonTapped)): + state.editTag = .init(mode: .edit, tag: state.tagItem[id: id]?.tag) + return .none + case .editTag(.presented(.delegate(.save))): state.editTag = nil return .send(.fetchTagsRequest) + case let .editTag(.presented(.delegate(.delete(tag)))): + if let index = state.selectedTags.firstIndex(of: tag) { + state.selectedTags.remove(at: index) + } + state.editTag = nil + return .concatenate([ + .send(.fetchTagsRequest), + .send(.delegate(.deleted(tag))) + ]) + case .editTag(.dismiss): state.editTag = nil return .none From 57821898de13c50cac493582669c7ae8994602f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Tue, 3 Oct 2023 15:11:31 +0900 Subject: [PATCH 14/19] =?UTF-8?q?feat:=20black=20or=20white=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DesignSystem/Sources/Color+Extension.swift | 12 ++++++++++-- .../Shared/DesignSystem/Sources/MinimalButton.swift | 2 +- .../Sources/Calendar/Cell/CalendarItemCellView.swift | 4 ++-- Projects/Toolinder/Feature/Sources/MainTabView.swift | 2 +- .../Sources/Components/Cell/TagItemCellView.swift | 2 +- .../Components/Cell/TradePreviewItemCellView.swift | 2 +- .../Interface/Sources/EditTag/EditTagStore.swift | 2 +- .../Sources/EditTicker/EditTickerStore.swift | 1 + .../Sources/EditTicker/EditTickerView.swift | 7 ++++--- 9 files changed, 22 insertions(+), 12 deletions(-) diff --git a/Projects/Folio/Shared/DesignSystem/Sources/Color+Extension.swift b/Projects/Folio/Shared/DesignSystem/Sources/Color+Extension.swift index 1933ce4..d7f9c63 100644 --- a/Projects/Folio/Shared/DesignSystem/Sources/Color+Extension.swift +++ b/Projects/Folio/Shared/DesignSystem/Sources/Color+Extension.swift @@ -33,10 +33,18 @@ public extension Color { ) } - static func blackOrWhite(_ isSelected: Bool = false) -> Self { - return isSelected ? Color(uiColor: .label) : Color(uiColor: .systemBackground) + static var foreground: Self { + return Color(uiColor: .label) } + static var background: Self { + return Color(uiColor: .systemBackground) + } + +// static func blackOrWhite(_ isSelected: Bool = false) -> Self { +// return isSelected ? Color(uiColor: .label) : Color(uiColor: .systemBackground) +// } + func toHex() -> String { let uic = UIColor(self) guard let components = uic.cgColor.components, components.count >= 3 else { diff --git a/Projects/Folio/Shared/DesignSystem/Sources/MinimalButton.swift b/Projects/Folio/Shared/DesignSystem/Sources/MinimalButton.swift index 57471eb..9843534 100644 --- a/Projects/Folio/Shared/DesignSystem/Sources/MinimalButton.swift +++ b/Projects/Folio/Shared/DesignSystem/Sources/MinimalButton.swift @@ -40,7 +40,7 @@ public struct MinimalButton: View { } .padding(.vertical, 10) }) - .background(.black) + .background(isActive ? Color.foreground : .gray) .clipShape( RoundedRectangle( cornerRadius: 8, diff --git a/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/Cell/CalendarItemCellView.swift b/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/Cell/CalendarItemCellView.swift index 6652866..24a7aa6 100644 --- a/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/Cell/CalendarItemCellView.swift +++ b/Projects/Toolinder/Feature/Calendar/Interface/Sources/Calendar/Cell/CalendarItemCellView.swift @@ -29,7 +29,7 @@ public struct CalendarItemCellView: View { Text("\(viewStore.state.date.day)") .font(.subheadline) .fontWeight(.semibold) - .foregroundStyle(Color.blackOrWhite(!viewStore.state.isSelected)) + .foregroundStyle(viewStore.state.isSelected ? Color.background : Color.foreground) Spacer() } @@ -41,7 +41,7 @@ public struct CalendarItemCellView: View { Spacer() } - .background(Color.blackOrWhite(viewStore.state.isSelected)) + .background(viewStore.state.isSelected ? Color.foreground : Color.background) .clipShape( RoundedRectangle( cornerRadius: 8, diff --git a/Projects/Toolinder/Feature/Sources/MainTabView.swift b/Projects/Toolinder/Feature/Sources/MainTabView.swift index 23e4705..a0fb1bb 100644 --- a/Projects/Toolinder/Feature/Sources/MainTabView.swift +++ b/Projects/Toolinder/Feature/Sources/MainTabView.swift @@ -47,7 +47,7 @@ public struct MainTabView: View { .onAppear { viewStore.send(.onAppear) } - .accentColor(Color.blackOrWhite(true)) + .accentColor(Color.foreground) } } } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellView.swift index 490f249..e9a9206 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TagItemCellView.swift @@ -24,7 +24,7 @@ public struct TagItemCellView: View { HStack(spacing: 10) { Circle() .fill(Color(hex: viewStore.state.tag.hex)) - .frame(width: 20, height: 20) + .frame(width: 15, height: 15) Text(viewStore.state.tag.name) .font(.caption2) diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TradePreviewItemCellView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TradePreviewItemCellView.swift index 1ed4e76..991ee2e 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TradePreviewItemCellView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TradePreviewItemCellView.swift @@ -29,7 +29,7 @@ public struct TradePreviewItemCellView: View { Text(viewStore.state.trade.ticker?.name ?? "") .font(.caption2) .fontWeight(.light) - .foregroundStyle(Color.blackOrWhite(!viewStore.state.isSelected)) + .foregroundStyle(viewStore.state.isSelected ? Color.background : Color.foreground) Spacer() } diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagStore.swift index 3c4709e..24c9b5a 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTag/EditTagStore.swift @@ -22,7 +22,7 @@ public struct EditTagStore: Reducer { public var title: LocalizedStringKey = "" public var tagName: String = "" - public var tagColor: Color = .blackOrWhite() + public var tagColor: Color = .foreground public init( mode: EditMode, diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift index e814a8c..47519bd 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerStore.swift @@ -48,6 +48,7 @@ public struct EditTickerStore: Reducer { self.name = ticker?.name ?? "" self.selectedTickerType = ticker?.type ?? .stock self.selectedCurrency = ticker?.currency ?? .dollar + self.selectedTags = ticker?.tags ?? [] self.tagItem = .init( uniqueElements: ticker?.tags?.map { tag in return .init(mode: .select, tag: tag) diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerView.swift index b294099..892ec38 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/EditTicker/EditTickerView.swift @@ -116,7 +116,7 @@ public struct EditTickerView: View { } private func nameView(viewStore: ViewStoreOf) -> some View { - TextField("Name", text: viewStore.binding(get: \.name, send: EditTickerStore.Action.setName)) + TextField("Name", text: viewStore.binding(get: \.name.localizedUppercase, send: EditTickerStore.Action.setName)) .foregroundStyle(.foreground) } @@ -140,16 +140,17 @@ public struct EditTickerView: View { private func tagView(viewStore: ViewStoreOf) -> some View { ScrollView(.horizontal) { - HStack { + HStack(spacing: .zero) { Button(action: { viewStore.send(.tagButtonTapped) }, label: { - Label(viewStore.selectedTags.isEmpty ? "Tag": "", systemImage: "tag.circle.fill") + Label(viewStore.state.selectedTags.isEmpty ? "Tag" : "", systemImage: "tag.circle.fill") .foregroundStyle(.foreground) }) ForEachStore(self.store.scope(state: \.tagItem, action: EditTickerStore.Action.tagItem(id:action:))) { TagItemCellView(store: $0) + .padding(.trailing) } } } From 81d98abf1a1ef2c1ff5528d462f38d8931a94856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Tue, 3 Oct 2023 17:22:52 +0900 Subject: [PATCH 15/19] =?UTF-8?q?feat:=20google=20analytics=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../Taget+Extensions/TargetDependency+Module.swift | 3 ++- .../Folio/Shared/DesignSystem/Sources/MinimalButton.swift | 2 +- Projects/Toolinder/App/Sources/AppDelegate.swift | 5 ++++- Projects/Toolinder/App/Sources/RootApp.swift | 2 ++ Tuist/Dependencies.swift | 4 +++- 6 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 0ea58a5..bf49e5f 100644 --- a/.gitignore +++ b/.gitignore @@ -121,3 +121,4 @@ Derived/ *.p12 master.key *.package.resolved +GoogleService-Info.plist diff --git a/Plugins/ModulePlugin/ProjectDescriptionHelpers/Taget+Extensions/TargetDependency+Module.swift b/Plugins/ModulePlugin/ProjectDescriptionHelpers/Taget+Extensions/TargetDependency+Module.swift index 07275d7..79e4aa4 100644 --- a/Plugins/ModulePlugin/ProjectDescriptionHelpers/Taget+Extensions/TargetDependency+Module.swift +++ b/Plugins/ModulePlugin/ProjectDescriptionHelpers/Taget+Extensions/TargetDependency+Module.swift @@ -119,7 +119,8 @@ public extension [TargetDependency] { ] case .Folio: return [ - .external(name: "GoogleMobileAds") + .external(name: "GoogleMobileAds"), + .external(name: "FirebaseAnalytics") ] default: return [] } diff --git a/Projects/Folio/Shared/DesignSystem/Sources/MinimalButton.swift b/Projects/Folio/Shared/DesignSystem/Sources/MinimalButton.swift index 9843534..af11440 100644 --- a/Projects/Folio/Shared/DesignSystem/Sources/MinimalButton.swift +++ b/Projects/Folio/Shared/DesignSystem/Sources/MinimalButton.swift @@ -40,7 +40,7 @@ public struct MinimalButton: View { } .padding(.vertical, 10) }) - .background(isActive ? Color.foreground : .gray) + .background(isActive ? Color.foreground : Color.foreground) //TODO: active 현재 미사용 .clipShape( RoundedRectangle( cornerRadius: 8, diff --git a/Projects/Toolinder/App/Sources/AppDelegate.swift b/Projects/Toolinder/App/Sources/AppDelegate.swift index 7614bed..508b59d 100644 --- a/Projects/Toolinder/App/Sources/AppDelegate.swift +++ b/Projects/Toolinder/App/Sources/AppDelegate.swift @@ -7,6 +7,8 @@ // import UIKit +import SwiftUI +import FirebaseCore import GoogleMobileAds @@ -22,7 +24,8 @@ class AppDelegate: NSObject, UIApplicationDelegate { GADMobileAds.sharedInstance().start(completionHandler: nil) } } - + + FirebaseApp.configure() return true } diff --git a/Projects/Toolinder/App/Sources/RootApp.swift b/Projects/Toolinder/App/Sources/RootApp.swift index 6bb1b1a..5760166 100644 --- a/Projects/Toolinder/App/Sources/RootApp.swift +++ b/Projects/Toolinder/App/Sources/RootApp.swift @@ -15,6 +15,8 @@ import ToolinderDomain @main struct RootApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate + var body: some Scene { WindowGroup { RootView( diff --git a/Tuist/Dependencies.swift b/Tuist/Dependencies.swift index a7eb0cf..9216c6a 100644 --- a/Tuist/Dependencies.swift +++ b/Tuist/Dependencies.swift @@ -4,7 +4,9 @@ let dependencies = Dependencies( swiftPackageManager: [ .remote(url: "https://github.com/googleads/swift-package-manager-google-mobile-ads", requirement: .upToNextMajor(from: "10.9.0")), .remote(url: "https://github.com/pointfreeco/swift-composable-architecture", requirement: .upToNextMajor(from: "1.2.0")), - .remote(url: "https://github.com/realm/realm-swift", requirement: .upToNextMajor(from: "10.42.2")) + .remote(url: "https://github.com/realm/realm-swift", requirement: .upToNextMajor(from: "10.42.2")), + .remote(url: "https://github.com/firebase/firebase-ios-sdk", requirement: .upToNextMajor(from: "10.15.0")) + ], platforms: [.iOS] ) From 2e61ca2efa5b4aa54557f38c8fc890edc36b77ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Tue, 3 Oct 2023 17:35:03 +0900 Subject: [PATCH 16/19] feat: rm chache --- .gitignore | 3 +- Projects/Dying/.package.resolved | 104 ------------------ Projects/Folio/.package.resolved | 158 --------------------------- Projects/Toolinder/.package.resolved | 23 ---- 4 files changed, 2 insertions(+), 286 deletions(-) delete mode 100644 Projects/Dying/.package.resolved delete mode 100644 Projects/Folio/.package.resolved delete mode 100644 Projects/Toolinder/.package.resolved diff --git a/.gitignore b/.gitignore index bf49e5f..90e4c2c 100644 --- a/.gitignore +++ b/.gitignore @@ -121,4 +121,5 @@ Derived/ *.p12 master.key *.package.resolved -GoogleService-Info.plist + +GoogleService-Info.plist \ No newline at end of file diff --git a/Projects/Dying/.package.resolved b/Projects/Dying/.package.resolved deleted file mode 100644 index 03774b2..0000000 --- a/Projects/Dying/.package.resolved +++ /dev/null @@ -1,104 +0,0 @@ -{ - "pins" : [ - { - "identity" : "combine-schedulers", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/combine-schedulers", - "state" : { - "revision" : "9dc9cbe4bc45c65164fa653a563d8d8db61b09bb", - "version" : "1.0.0" - } - }, - { - "identity" : "swift-case-paths", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-case-paths", - "state" : { - "revision" : "5da6989aae464f324eef5c5b52bdb7974725ab81", - "version" : "1.0.0" - } - }, - { - "identity" : "swift-clocks", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-clocks", - "state" : { - "revision" : "d1fd837326aa719bee979bdde1f53cd5797443eb", - "version" : "1.0.0" - } - }, - { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections", - "state" : { - "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", - "version" : "1.0.4" - } - }, - { - "identity" : "swift-composable-architecture", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-composable-architecture", - "state" : { - "revision" : "195284b94b799b326729640453f547f08892293a", - "version" : "1.0.0" - } - }, - { - "identity" : "swift-concurrency-extras", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-concurrency-extras", - "state" : { - "revision" : "ea631ce892687f5432a833312292b80db238186a", - "version" : "1.0.0" - } - }, - { - "identity" : "swift-custom-dump", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-custom-dump", - "state" : { - "revision" : "edd66cace818e1b1c6f1b3349bb1d8e00d6f8b01", - "version" : "1.0.0" - } - }, - { - "identity" : "swift-dependencies", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-dependencies", - "state" : { - "revision" : "4e1eb6e28afe723286d8cc60611237ffbddba7c5", - "version" : "1.0.0" - } - }, - { - "identity" : "swift-identified-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-identified-collections", - "state" : { - "revision" : "d1e45f3e1eee2c9193f5369fa9d70a6ddad635e8", - "version" : "1.0.0" - } - }, - { - "identity" : "swiftui-navigation", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swiftui-navigation", - "state" : { - "revision" : "f5bcdac5b6bb3f826916b14705f37a3937c2fd34", - "version" : "1.0.0" - } - }, - { - "identity" : "xctest-dynamic-overlay", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", - "state" : { - "revision" : "302891700c7fa3b92ebde9fe7b42933f8349f3c7", - "version" : "1.0.0" - } - } - ], - "version" : 2 -} diff --git a/Projects/Folio/.package.resolved b/Projects/Folio/.package.resolved deleted file mode 100644 index 27d02ec..0000000 --- a/Projects/Folio/.package.resolved +++ /dev/null @@ -1,158 +0,0 @@ -{ - "pins" : [ - { - "identity" : "combine-schedulers", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/combine-schedulers", - "state" : { - "revision" : "9dc9cbe4bc45c65164fa653a563d8d8db61b09bb", - "version" : "1.0.0" - } - }, - { - "identity" : "googleappmeasurement", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/GoogleAppMeasurement.git", - "state" : { - "revision" : "03b9beee1a61f62d32c521e172e192a1663a5e8b", - "version" : "10.13.0" - } - }, - { - "identity" : "googleutilities", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/GoogleUtilities.git", - "state" : { - "revision" : "c38ce365d77b04a9a300c31061c5227589e5597b", - "version" : "7.11.5" - } - }, - { - "identity" : "nanopb", - "kind" : "remoteSourceControl", - "location" : "https://github.com/firebase/nanopb.git", - "state" : { - "revision" : "819d0a2173aff699fb8c364b6fb906f7cdb1a692", - "version" : "2.30909.0" - } - }, - { - "identity" : "promises", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/promises.git", - "state" : { - "revision" : "e70e889c0196c76d22759eb50d6a0270ca9f1d9e", - "version" : "2.3.1" - } - }, - { - "identity" : "swift-case-paths", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-case-paths", - "state" : { - "revision" : "5da6989aae464f324eef5c5b52bdb7974725ab81", - "version" : "1.0.0" - } - }, - { - "identity" : "swift-clocks", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-clocks", - "state" : { - "revision" : "d1fd837326aa719bee979bdde1f53cd5797443eb", - "version" : "1.0.0" - } - }, - { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections", - "state" : { - "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", - "version" : "1.0.4" - } - }, - { - "identity" : "swift-composable-architecture", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-composable-architecture", - "state" : { - "revision" : "a7c1f799b55ecb418f85094b142565834f7ee7c7", - "version" : "1.2.0" - } - }, - { - "identity" : "swift-concurrency-extras", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-concurrency-extras", - "state" : { - "revision" : "ea631ce892687f5432a833312292b80db238186a", - "version" : "1.0.0" - } - }, - { - "identity" : "swift-custom-dump", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-custom-dump", - "state" : { - "revision" : "edd66cace818e1b1c6f1b3349bb1d8e00d6f8b01", - "version" : "1.0.0" - } - }, - { - "identity" : "swift-dependencies", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-dependencies", - "state" : { - "revision" : "4e1eb6e28afe723286d8cc60611237ffbddba7c5", - "version" : "1.0.0" - } - }, - { - "identity" : "swift-identified-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-identified-collections", - "state" : { - "revision" : "d1e45f3e1eee2c9193f5369fa9d70a6ddad635e8", - "version" : "1.0.0" - } - }, - { - "identity" : "swift-package-manager-google-mobile-ads", - "kind" : "remoteSourceControl", - "location" : "https://github.com/googleads/swift-package-manager-google-mobile-ads", - "state" : { - "revision" : "1a6faf6b9b82ddf8780f678745381b8628711077", - "version" : "10.9.0" - } - }, - { - "identity" : "swift-package-manager-google-user-messaging-platform", - "kind" : "remoteSourceControl", - "location" : "https://github.com/googleads/swift-package-manager-google-user-messaging-platform.git", - "state" : { - "revision" : "129fa838520cd02174f890ae0cfe0242e60714ae", - "version" : "2.1.0" - } - }, - { - "identity" : "swiftui-navigation", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swiftui-navigation", - "state" : { - "revision" : "f5bcdac5b6bb3f826916b14705f37a3937c2fd34", - "version" : "1.0.0" - } - }, - { - "identity" : "xctest-dynamic-overlay", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", - "state" : { - "revision" : "23cbf2294e350076ea4dbd7d5d047c1e76b03631", - "version" : "1.0.2" - } - } - ], - "version" : 2 -} diff --git a/Projects/Toolinder/.package.resolved b/Projects/Toolinder/.package.resolved deleted file mode 100644 index 6963374..0000000 --- a/Projects/Toolinder/.package.resolved +++ /dev/null @@ -1,23 +0,0 @@ -{ - "pins" : [ - { - "identity" : "realm-core", - "kind" : "remoteSourceControl", - "location" : "https://github.com/realm/realm-core", - "state" : { - "revision" : "c04f5e401a1ec682e6b08b1ee157e19a0f834a5f", - "version" : "13.17.1" - } - }, - { - "identity" : "realm-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/realm/realm-swift", - "state" : { - "revision" : "330a239712af77a3b0926b9ffa9582302a0b9923", - "version" : "10.42.1" - } - } - ], - "version" : 2 -} From 3575332c3698059f4785b698b6a980a6fa71abb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Tue, 3 Oct 2023 18:19:19 +0900 Subject: [PATCH 17/19] =?UTF-8?q?feat:=20icloud=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Toolinder/App/Sources/RootApp.swift | 5 +++++ Projects/Toolinder/App/ToolinderIOS.entitlements | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Projects/Toolinder/App/Sources/RootApp.swift b/Projects/Toolinder/App/Sources/RootApp.swift index 5760166..ef2b685 100644 --- a/Projects/Toolinder/App/Sources/RootApp.swift +++ b/Projects/Toolinder/App/Sources/RootApp.swift @@ -25,6 +25,11 @@ struct RootApp: App { ._printChanges() } ) + .modelContainer(for: [ + Ticker.self, + Trade.self, + Tag.self + ]) .onAppear(perform: UIApplication.shared.hideKeyboard) } } diff --git a/Projects/Toolinder/App/ToolinderIOS.entitlements b/Projects/Toolinder/App/ToolinderIOS.entitlements index 90bda52..9c1f471 100644 --- a/Projects/Toolinder/App/ToolinderIOS.entitlements +++ b/Projects/Toolinder/App/ToolinderIOS.entitlements @@ -2,17 +2,17 @@ - com.apple.security.app-sandbox - aps-environment development com.apple.developer.icloud-container-identifiers - iCloud.toolinder + iCloud.com.tamsadan.toolinder com.apple.developer.icloud-services CloudKit + com.apple.security.app-sandbox + From baf9f10a742584c52e6539c0e79e26121b7538a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Tue, 3 Oct 2023 20:42:02 +0900 Subject: [PATCH 18/19] =?UTF-8?q?feat:=20mypage=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ExistingUserPolicyView.swift | 50 ------------------ .../Sources/Main/MyPageMainStore.swift | 8 +-- .../Sources/Main/MyPageMainView.swift | 4 +- .../MyPageNavigationStackStore.swift | 12 ++--- .../MyPageNavigationStackView.swift | 8 +-- .../WhatIsNewStore.swift} | 2 +- .../Sources/WhatIsNew/WhatIsNewView.swift | 51 +++++++++++++++++++ .../Components/Cell/TradeItemCellView.swift | 1 - 8 files changed, 68 insertions(+), 68 deletions(-) delete mode 100644 Projects/Toolinder/Feature/MyPage/Interface/Sources/ExistingUserPolicy/ExistingUserPolicyView.swift rename Projects/Toolinder/Feature/MyPage/Interface/Sources/{ExistingUserPolicy/ExistingUserPolicyStore.swift => WhatIsNew/WhatIsNewStore.swift} (91%) create mode 100644 Projects/Toolinder/Feature/MyPage/Interface/Sources/WhatIsNew/WhatIsNewView.swift diff --git a/Projects/Toolinder/Feature/MyPage/Interface/Sources/ExistingUserPolicy/ExistingUserPolicyView.swift b/Projects/Toolinder/Feature/MyPage/Interface/Sources/ExistingUserPolicy/ExistingUserPolicyView.swift deleted file mode 100644 index d851b3e..0000000 --- a/Projects/Toolinder/Feature/MyPage/Interface/Sources/ExistingUserPolicy/ExistingUserPolicyView.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// ExistingUserPolicyView.swift -// ToolinderFeatureMyPageDemo -// -// Created by 송영모 on 2023/09/14. -// - -import SwiftUI - -import ComposableArchitecture - -public struct ExistingUserPolicyView: View { - let store: StoreOf - - public init(store: StoreOf) { - self.store = store - } - - public var body: some View { - WithViewStore(self.store, observe: { $0 }) { viewStore in - ScrollView { - HStack { - VStack(alignment: .leading) { - Text("1 버전을 사용해주신 고마운 사용자분들께") - .font(.body) - .padding(.bottom, 5) - - Text("여러분께 아주 죄송한 마음 뿐입니다.") - .font(.headline) - - Text("우선 지금부터 하는 얘기는 기존 사용자들의 데이터를 살리지 못하는 결정을 내리게된 이유를 설명드리려고 합니다. 앱을 업데이트 했는데, 데이터가 모두 사라지는 현상이 있었습니다. 그래서 이를 해결하고자 하였지만, 아주 오래전 작성된 코드(물론 제가 작성했었음)는 문제가 있었고 현재 모두 새로운 것으로 고쳤습니다. 그래서 현재 버전부터는 모든 문제가 해결되었지만, 기존에 발생하고 있던 문제는 심지어 앱을 강제로 꺼지는 버그도 존재했습니다. 이를 해결하는 원인 파악이 어렵고 꽤 많은 곳에서 버그들이 발생하기 때문에 아예 새로 다시 만들자는 판단을 하였습니다.") - .font(.caption) - .padding(.bottom, 5) - - Text("더 나은 사용성을 위하여 피드백을 적극적으로 수용하겠습니다.") - .font(.headline) - - Text("앞으로는 유저 여러분의 소중한 피드백을 하나하나씩 반영해 나가겠습니다! 긴 글을 읽어주셔서 감사합니다. 다시 한번 죄송합니다. 마이페이지의 설문을 간단하게 달아놓았습니다. 언제든지 피드백을 주시면 곧바로 반영하겠습니다.") - .font(.caption) - .padding(.bottom, 5) - } - - Spacer() - } - .padding() - } - .navigationTitle("Existing User Policy") - } - } -} diff --git a/Projects/Toolinder/Feature/MyPage/Interface/Sources/Main/MyPageMainStore.swift b/Projects/Toolinder/Feature/MyPage/Interface/Sources/Main/MyPageMainStore.swift index 5fe8f74..e25e583 100644 --- a/Projects/Toolinder/Feature/MyPage/Interface/Sources/Main/MyPageMainStore.swift +++ b/Projects/Toolinder/Feature/MyPage/Interface/Sources/Main/MyPageMainStore.swift @@ -20,10 +20,10 @@ public struct MyPageMainStore: Reducer { case onAppear case delegate(Delegate) - case existingUserPolicyTapped + case whatIsNew public enum Delegate: Equatable { - case existingUserPolicy + case whatIsNew } } @@ -33,8 +33,8 @@ public struct MyPageMainStore: Reducer { case .onAppear: return .none - case .existingUserPolicyTapped: - return .send(.delegate(.existingUserPolicy)) + case .whatIsNew: + return .send(.delegate(.whatIsNew)) default: return .none diff --git a/Projects/Toolinder/Feature/MyPage/Interface/Sources/Main/MyPageMainView.swift b/Projects/Toolinder/Feature/MyPage/Interface/Sources/Main/MyPageMainView.swift index 102b59c..76726ae 100644 --- a/Projects/Toolinder/Feature/MyPage/Interface/Sources/Main/MyPageMainView.swift +++ b/Projects/Toolinder/Feature/MyPage/Interface/Sources/Main/MyPageMainView.swift @@ -46,7 +46,7 @@ public struct MyPageMainView: View { HStack { Label( title: { - Text("Existing User Policy") + Text("What's New \(Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "")") }, icon: { Image(systemName: "info.circle.fill") .foregroundStyle(.blue) @@ -55,7 +55,7 @@ public struct MyPageMainView: View { Spacer() Button( action: { - viewStore.send(.existingUserPolicyTapped) + viewStore.send(.whatIsNew) }, label: { Image(systemName: "chevron.right") diff --git a/Projects/Toolinder/Feature/MyPage/Interface/Sources/NavigationStack/MyPageNavigationStackStore.swift b/Projects/Toolinder/Feature/MyPage/Interface/Sources/NavigationStack/MyPageNavigationStackStore.swift index 6260252..c730365 100644 --- a/Projects/Toolinder/Feature/MyPage/Interface/Sources/NavigationStack/MyPageNavigationStackStore.swift +++ b/Projects/Toolinder/Feature/MyPage/Interface/Sources/NavigationStack/MyPageNavigationStackStore.swift @@ -29,16 +29,16 @@ public struct MyPageNavigationStackStore: Reducer { public struct Path: Reducer { public enum State: Equatable { - case existingUserPolicy(ExistingUserPolicyStore.State) + case whatIsNew(WhatIsNewStore.State) } public enum Action: Equatable { - case existingUserPolicy(ExistingUserPolicyStore.Action) + case whatIsNew(WhatIsNewStore.Action) } public var body: some Reducer { - Scope(state: /State.existingUserPolicy, action: /Action.existingUserPolicy) { - ExistingUserPolicyStore() + Scope(state: /State.whatIsNew, action: /Action.whatIsNew) { + WhatIsNewStore() } } } @@ -51,8 +51,8 @@ public struct MyPageNavigationStackStore: Reducer { case .onAppear: return .none - case .main(.delegate(.existingUserPolicy)): - state.path.append(.existingUserPolicy(.init())) + case .main(.delegate(.whatIsNew)): + state.path.append(.whatIsNew(.init())) return .none default: diff --git a/Projects/Toolinder/Feature/MyPage/Interface/Sources/NavigationStack/MyPageNavigationStackView.swift b/Projects/Toolinder/Feature/MyPage/Interface/Sources/NavigationStack/MyPageNavigationStackView.swift index 723eca3..fc48edd 100644 --- a/Projects/Toolinder/Feature/MyPage/Interface/Sources/NavigationStack/MyPageNavigationStackView.swift +++ b/Projects/Toolinder/Feature/MyPage/Interface/Sources/NavigationStack/MyPageNavigationStackView.swift @@ -33,11 +33,11 @@ public struct MyPageNavigationStackView: View { } } destination: { switch $0 { - case .existingUserPolicy: + case .whatIsNew: CaseLet( - /MyPageNavigationStackStore.Path.State.existingUserPolicy, - action: MyPageNavigationStackStore.Path.Action.existingUserPolicy, - then: ExistingUserPolicyView.init(store:)) + /MyPageNavigationStackStore.Path.State.whatIsNew, + action: MyPageNavigationStackStore.Path.Action.whatIsNew, + then: WhatIsNewView.init(store:)) } } } diff --git a/Projects/Toolinder/Feature/MyPage/Interface/Sources/ExistingUserPolicy/ExistingUserPolicyStore.swift b/Projects/Toolinder/Feature/MyPage/Interface/Sources/WhatIsNew/WhatIsNewStore.swift similarity index 91% rename from Projects/Toolinder/Feature/MyPage/Interface/Sources/ExistingUserPolicy/ExistingUserPolicyStore.swift rename to Projects/Toolinder/Feature/MyPage/Interface/Sources/WhatIsNew/WhatIsNewStore.swift index 164d162..3bef097 100644 --- a/Projects/Toolinder/Feature/MyPage/Interface/Sources/ExistingUserPolicy/ExistingUserPolicyStore.swift +++ b/Projects/Toolinder/Feature/MyPage/Interface/Sources/WhatIsNew/WhatIsNewStore.swift @@ -9,7 +9,7 @@ import Foundation import ComposableArchitecture -public struct ExistingUserPolicyStore: Reducer { +public struct WhatIsNewStore: Reducer { public init() {} public struct State: Equatable { diff --git a/Projects/Toolinder/Feature/MyPage/Interface/Sources/WhatIsNew/WhatIsNewView.swift b/Projects/Toolinder/Feature/MyPage/Interface/Sources/WhatIsNew/WhatIsNewView.swift new file mode 100644 index 0000000..06ae633 --- /dev/null +++ b/Projects/Toolinder/Feature/MyPage/Interface/Sources/WhatIsNew/WhatIsNewView.swift @@ -0,0 +1,51 @@ +// +// ExistingUserPolicyView.swift +// ToolinderFeatureMyPageDemo +// +// Created by 송영모 on 2023/09/14. +// + +import SwiftUI + +import ComposableArchitecture + +public struct WhatIsNewView: View { + let store: StoreOf + + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + WithViewStore(self.store, observe: { $0 }) { viewStore in + ScrollView { + HStack { + VStack(alignment: .leading) { + Text("투린더를 사용해주신 고마운 사용자분들께") + .font(.body) + .padding(.bottom, 5) + + Text( +""" +유저가 애초에 많은 서비스는 아니었지만, 꾸준히 사용해주시는 10명 정도의 사용자분들께 작성하는 편지라고 생각하시면 감사할 것 같습니다. +우선 먼저 감사하다는 말씀을 드립니다. 댓글도 별로 안달리고 평점도 낮은 앱이지만 작은 관심은 항상 큰 도움이 되었습니다. 그리고 업데이트를 결정한 이유도 모두 꾸준히 사용해주시는 분들이 계셔서라고 할 수 있습니다. + +하지만 이 다음부터 하는 이야기는 모두 죄송하다는 말 뿐이어서 저도 속상한 마음으로 작성 중입니다. 기술적으로 투린더의 데이터를 살리지 못하는 버그가 존재했고, 모든 것을 살리기에는 시간적으로 많은 시간이 걸리는 것으로 파악을 하였습니다. 저의 첫번째 앱이기도 하고 그때 당시에 실력이 좋지 못해서 큰 그림을 그리며 설계를 하지 못해서 이렇게 되었습니다. 살리려고 노력을 많이 해봤지만, 아예 구조자체를 바꾸려고 하는 상황이어서 너무 많은 시간과 노력이 필요하다는 결론을 지었습니다. 그곳에 사용하는 시간을 앞으로의 새로운 앱에 집중하고자 하였습니다. + +이런 상황은 다시 발생해서는 안된다고 생각합니다. 앞으로는 더욱더 나은 서비스를 만들도록 노력하겠습니다. + +긴 글 읽어주셔서 감사합니다. +""" + ) + .font(.caption) + .padding(.bottom, 5) + } + + Spacer() + } + .padding() + } + .navigationTitle("What's New \(Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "")") + } + } +} diff --git a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TradeItemCellView.swift b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TradeItemCellView.swift index 69cd08e..0e311b1 100644 --- a/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TradeItemCellView.swift +++ b/Projects/Toolinder/Feature/Trade/Interface/Sources/Components/Cell/TradeItemCellView.swift @@ -58,7 +58,6 @@ public struct TradeItemCellView: View { .font(.caption2) } } - .frame(maxWidth: 100) viewStore.state.trade.ticker?.type.image .font(.title3) From d8ed91eea09446c7b8e05b96daaf856c6ea755b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=89=E1=85=A9=E1=86=BC=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=86=E1=85=A9?= Date: Tue, 3 Oct 2023 22:11:01 +0900 Subject: [PATCH 19/19] =?UTF-8?q?build:=20v2.0.0=20=EC=B6=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Toolinder/App/Support/ToolinderAppIOS-Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Toolinder/App/Support/ToolinderAppIOS-Info.plist b/Projects/Toolinder/App/Support/ToolinderAppIOS-Info.plist index 16f5986..e4e5ae3 100644 --- a/Projects/Toolinder/App/Support/ToolinderAppIOS-Info.plist +++ b/Projects/Toolinder/App/Support/ToolinderAppIOS-Info.plist @@ -7,7 +7,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Toolinder + Toff CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier