diff --git a/.serena/.gitignore b/.serena/.gitignore new file mode 100644 index 0000000..14d86ad --- /dev/null +++ b/.serena/.gitignore @@ -0,0 +1 @@ +/cache diff --git a/.serena/project.yml b/.serena/project.yml new file mode 100644 index 0000000..a9bf6d0 --- /dev/null +++ b/.serena/project.yml @@ -0,0 +1,117 @@ +# the name by which the project can be referenced within Serena +project_name: "exchanges-app" + + +# list of languages for which language servers are started; choose from: +# al bash clojure cpp csharp +# csharp_omnisharp dart elixir elm erlang +# fortran fsharp go groovy haskell +# java julia kotlin lua markdown +# matlab nix pascal perl php +# php_phpactor powershell python python_jedi r +# rego ruby ruby_solargraph rust scala +# swift terraform toml typescript typescript_vts +# vue yaml zig +# (This list may be outdated. For the current list, see values of Language enum here: +# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py +# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) +# Note: +# - For C, use cpp +# - For JavaScript, use typescript +# - For Free Pascal/Lazarus, use pascal +# Special requirements: +# Some languages require additional setup/installations. +# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers +# When using multiple languages, the first language server that supports a given file will be used for that file. +# The first language is the default language and the respective language server will be used as a fallback. +# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. +languages: +- swift + +# the encoding used by text files in the project +# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings +encoding: "utf-8" + +# whether to use project's .gitignore files to ignore files +ignore_all_files_in_gitignore: true + +# list of additional paths to ignore in this project. +# Same syntax as gitignore, so you can use * and **. +# Note: global ignored_paths from serena_config.yml are also applied additively. +ignored_paths: [] + +# whether the project is in read-only mode +# If set to true, all editing tools will be disabled and attempts to use them will result in an error +# Added on 2025-04-18 +read_only: false + +# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. +# Below is the complete list of tools for convenience. +# To make sure you have the latest list of tools, and to view their descriptions, +# execute `uv run scripts/print_tool_overview.py`. +# +# * `activate_project`: Activates a project by name. +# * `check_onboarding_performed`: Checks whether project onboarding was already performed. +# * `create_text_file`: Creates/overwrites a file in the project directory. +# * `delete_lines`: Deletes a range of lines within a file. +# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. +# * `execute_shell_command`: Executes a shell command. +# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. +# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). +# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). +# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. +# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. +# * `initial_instructions`: Gets the initial instructions for the current project. +# Should only be used in settings where the system prompt cannot be set, +# e.g. in clients you have no control over, like Claude Desktop. +# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. +# * `insert_at_line`: Inserts content at a given line in a file. +# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. +# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). +# * `list_memories`: Lists memories in Serena's project-specific memory store. +# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). +# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). +# * `read_file`: Reads a file within the project directory. +# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. +# * `remove_project`: Removes a project from the Serena configuration. +# * `replace_lines`: Replaces a range of lines within a file with new content. +# * `replace_symbol_body`: Replaces the full definition of a symbol. +# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. +# * `search_for_pattern`: Performs a search for a pattern in the project. +# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. +# * `switch_modes`: Activates modes by providing a list of their names +# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. +# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. +# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. +# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. +excluded_tools: [] + +# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default) +included_optional_tools: [] + +# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. +# This cannot be combined with non-empty excluded_tools or included_optional_tools. +fixed_tools: [] + +# list of mode names to that are always to be included in the set of active modes +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this setting overrides the global configuration. +# Set this to [] to disable base modes for this project. +# Set this to a list of mode names to always include the respective modes for this project. +base_modes: + +# list of mode names that are to be activated by default. +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this overrides the setting from the global configuration (serena_config.yml). +# This setting can, in turn, be overridden by CLI parameters (--mode). +default_modes: + +# initial prompt for the project. It will always be given to the LLM upon activating the project +# (contrary to the memories, which are loaded on demand). +initial_prompt: "" + +# override of the corresponding setting in serena_config.yml, see the documentation there. +# If null or missing, the value from the global config is used. +symbol_info_budget: diff --git a/Projects/DesignSystem/Sources/Components/InfoCell.swift b/Projects/DesignSystem/Sources/Components/InfoCell.swift index 8b0674a..53e2190 100644 --- a/Projects/DesignSystem/Sources/Components/InfoCell.swift +++ b/Projects/DesignSystem/Sources/Components/InfoCell.swift @@ -4,27 +4,6 @@ public final class InfoCell: UITableViewCell { public static let identifier = "InfoCell" - // MARK: - Models - public enum State { - case loaded(Model) - case partialLoaded(String) - case loading - } - - public struct Model { - public let url: String - public let title: String - public let subtitle: String - public let detail: String - - public init(url: String, title: String, subtitle: String, detail: String) { - self.url = url - self.title = title - self.subtitle = subtitle - self.detail = detail - } - } - // MARK: - UI Components private lazy var containerView: UIView = { let view = UIView() @@ -45,9 +24,11 @@ public final class InfoCell: UITableViewCell { private lazy var chevronImageView: UIImageView = { let config = UIImage.SymbolConfiguration(pointSize: 14, weight: .semibold) - let imageView = UIImageView(image: UIImage(systemName: "chevron.right", withConfiguration: config)) + let imageView = UIImageView(image: UIImage(systemName: "chevron.right", + withConfiguration: config)) imageView.tintColor = .tertiaryLabel imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.isHidden = true return imageView }() @@ -59,6 +40,8 @@ public final class InfoCell: UITableViewCell { return stack }() + private let defaultImage = UIImage(systemName: "bahtsign.bank.building.fill") + // MARK: - Init override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -69,11 +52,18 @@ public final class InfoCell: UITableViewCell { public override func prepareForReuse() { super.prepareForReuse() - [iconImageView, titleLabel, subtitleLabel, detailLabel].forEach { $0.setSkeleton(false) } + [ + iconImageView, + titleLabel, + subtitleLabel, + detailLabel + ].forEach { $0.setSkeleton(false) } iconImageView.image = nil titleLabel.text = nil subtitleLabel.text = nil detailLabel.text = nil + detailLabel.isHidden = false + chevronImageView.isHidden = true } // MARK: - Public Configuration @@ -85,23 +75,81 @@ public final class InfoCell: UITableViewCell { titleLabel.text = model.title subtitleLabel.text = model.subtitle detailLabel.text = model.detail - iconImageView.image = UIImage(systemName: "bahtsign.bank.building.fill") + detailLabel.isHidden = model.detail == nil + iconImageView.image = model.defaultImage ?? defaultImage loadImage(from: model.url) + chevronImageView.isHidden = model.hideChevron - case let .partialLoaded(title): - iconImageView.image = UIImage(systemName: "bahtsign.bank.building.fill") - titleLabel.text = title - subtitleLabel.text = " "; subtitleLabel.setSkeleton(true) - detailLabel.text = " "; detailLabel.setSkeleton(true) + case let .partialLoaded(model): + iconImageView.image = defaultImage + titleLabel.text = model.title + subtitleLabel.text = " " + subtitleLabel.setSkeleton(true) + detailLabel.isHidden = model.lines < 3 + if model.lines >= 3 { + detailLabel.text = " " + detailLabel.setSkeleton(true) + } + chevronImageView.isHidden = model.hideChevron - case .loading: - iconImageView.setSkeleton(true, cornerRadius: 11) + case let .loading(lines): + iconImageView.setSkeleton(true, cornerRadius: 12) titleLabel.text = " " titleLabel.setSkeleton(true) subtitleLabel.text = " " subtitleLabel.setSkeleton(true) - detailLabel.text = " " - detailLabel.setSkeleton(true) + detailLabel.isHidden = lines < 3 + if lines >= 3 { + detailLabel.text = " " + detailLabel.setSkeleton(true) + } + chevronImageView.isHidden = true + } + } +} + +// MARK: - Models +extension InfoCell { + public enum State { + case loaded(LoadedModel) + case partialLoaded(PartialModel) + case loading(lines: Int = 3) + } + + public struct LoadedModel { + public let url: String + public let title: String + public let subtitle: String + public let detail: String? + public let defaultImage: UIImage? + public let hideChevron: Bool + + public init(url: String, + title: String, + subtitle: String, + detail: String? = nil, + defaultImage: UIImage? = nil, + hiddenChevron: Bool = false) { + self.url = url + self.title = title + self.subtitle = subtitle + self.detail = detail + self.defaultImage = defaultImage + self.hideChevron = hiddenChevron + } + } + + public struct PartialModel { + public let title: String + public let hideChevron: Bool + public let lines: Int + + public init(title: String, + hiddenChevron: Bool = false, + lines: Int = 3) { + self.title = title + self.hideChevron = hiddenChevron + self.lines = lines } } } @@ -121,7 +169,7 @@ private extension InfoCell { imageView.contentMode = .scaleAspectFill imageView.clipsToBounds = true imageView.layer.cornerRadius = 12 - imageView.backgroundColor = .secondarySystemFill + imageView.backgroundColor = .clear imageView.translatesAutoresizingMaskIntoConstraints = false return imageView } @@ -132,7 +180,9 @@ private extension InfoCell { do { let (data, _) = try await URLSession.shared.data(from: url) if let image = UIImage(data: data) { - await MainActor.run { iconImageView.image = image } + await MainActor.run { + iconImageView.image = image + } } } catch {} } @@ -206,20 +256,33 @@ import SwiftUI subtitle: "Vol: $1234.23", detail: "Date launched: 12/03/2025" ))) + view.backgroundColor = .gray return view } @available(iOS 17.0, *) #Preview("Partial Loaded") { let view = InfoCell() - view.configure(state: .partialLoaded("Binance")) + view.configure(state: .partialLoaded(.init( + title: "Binance" + ))) + view.backgroundColor = .gray + return view +} + +@available(iOS 17.0, *) +#Preview("Loading - 3 lines") { + let view = InfoCell() + view.configure(state: .loading()) + view.backgroundColor = .gray return view } @available(iOS 17.0, *) -#Preview("Loading") { +#Preview("Loading - 2 lines") { let view = InfoCell() - view.configure(state: .loading) + view.configure(state: .loading(lines: 2)) + view.backgroundColor = .gray return view } #endif diff --git a/Projects/DesignSystem/Tests/Components/ErrorViewSnapshotTests.swift b/Projects/DesignSystem/Tests/Components/ErrorViewSnapshotTests.swift index d95c88a..310cf7d 100644 --- a/Projects/DesignSystem/Tests/Components/ErrorViewSnapshotTests.swift +++ b/Projects/DesignSystem/Tests/Components/ErrorViewSnapshotTests.swift @@ -13,7 +13,7 @@ import Testing @testable import DesignSystem @MainActor -@Suite() +@Suite struct ErrorViewSnapshotTest { @Test("GIVEN ErrorView THEN it should configure with default values") func testConfigureDefaultValues() throws { diff --git a/Projects/DesignSystem/Tests/Components/InfoCellSnapshotTests.swift b/Projects/DesignSystem/Tests/Components/InfoCellSnapshotTests.swift index 0f2e28f..51eea08 100644 --- a/Projects/DesignSystem/Tests/Components/InfoCellSnapshotTests.swift +++ b/Projects/DesignSystem/Tests/Components/InfoCellSnapshotTests.swift @@ -16,34 +16,77 @@ import Testing struct InfoCellSnapshotTests { @Test("GIVEN InfoCell WHEN loading THEN present view with loading state") func loading() throws { - let sut = makeSut(state: .loading) + let sut = makeSut(state: .loading()) assertSnapshot(of: sut, as: .image) } @Test("GIVEN InfoCell WHEN partialLoaded THEN present view with partial information") func partialLoaded() throws { - let sut = makeSut(state: .partialLoaded("Binance")) + let sut = makeSut(state: .partialLoaded(.init(title: "Binance"))) assertSnapshot(of: sut, as: .image) } - @Test("GIVEN InfoCell WHEN partialLoaded THEN present view with partial information") + @Test("GIVEN InfoCell WHEN partialLoaded AND hide chevron THEN present view with partial information") + func partialLoadedWithouChevron() throws { + let sut = makeSut(state: .partialLoaded(.init(title: "Binance", + hiddenChevron: true))) + + assertSnapshot(of: sut, as: .image) + } + + @Test("GIVEN InfoCell WHEN partialLoaded AND hide chevron THEN present view with partial information") func loaded() throws { - let infos = InfoCell.Model(url: "", - title: "Binance", - subtitle: "Vol: $1234.23", - detail: "Date launched: 12/03/2025") + let infos = InfoCell.LoadedModel(url: "", + title: "Binance", + subtitle: "Vol: $1234.23", + detail: "Date launched: 12/03/2025") + let sut = makeSut(state: .loaded(infos)) + + assertSnapshot(of: sut, as: .image) + } + + @Test("GIVEN InfoCell WHEN partialLoaded THEN present view with partial information") + func loadedWithoutChevron() throws { + let infos = InfoCell.LoadedModel(url: "", + title: "Binance", + subtitle: "Vol: $1234.23", + detail: "Date launched: 12/03/2025", + hiddenChevron: true) let sut = makeSut(state: .loaded(infos)) assertSnapshot(of: sut, as: .image) } + + @Test("GIVEN InfoCell WHEN loading AND 2 lines THEN present view with loading state without detail row") + func loading2Lines() throws { + let sut = makeSut(state: .loading(lines: 2), height: 88) + + assertSnapshot(of: sut, as: .image) + } + + @Test("GIVEN InfoCell WHEN partialLoaded AND 2 lines THEN present view with partial information without detail row") + func partialLoaded2Lines() throws { + let sut = makeSut(state: .partialLoaded(.init(title: "Bitcoin", lines: 2)), height: 88) + + assertSnapshot(of: sut, as: .image) + } + + @Test("GIVEN InfoCell WHEN partialLoaded AND 2 lines AND hide chevron THEN present view with partial information without detail row") + func partialLoaded2LinesWithoutChevron() throws { + let sut = makeSut(state: .partialLoaded(.init(title: "Bitcoin", + hiddenChevron: true, + lines: 2)), height: 88) + + assertSnapshot(of: sut, as: .image) + } } extension InfoCellSnapshotTests { - func makeSut(state: InfoCell.State) -> InfoCell { + func makeSut(state: InfoCell.State, height: CGFloat = 120) -> InfoCell { let sut = InfoCell() - sut.frame = CGRect(x: 0, y: 0, width: 402, height: 120) + sut.frame = CGRect(x: 0, y: 0, width: 402, height: height) sut.backgroundColor = .white sut.configure(state: state) diff --git a/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/loaded.1.png b/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/loaded.1.png index fae05c5..55ed43c 100644 Binary files a/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/loaded.1.png and b/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/loaded.1.png differ diff --git a/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/loadedWithoutChevron.1.png b/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/loadedWithoutChevron.1.png new file mode 100644 index 0000000..955ff4f Binary files /dev/null and b/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/loadedWithoutChevron.1.png differ diff --git a/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/loading.1.png b/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/loading.1.png index e809061..4a5d50a 100644 Binary files a/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/loading.1.png and b/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/loading.1.png differ diff --git a/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/loading2Lines.1.png b/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/loading2Lines.1.png new file mode 100644 index 0000000..64de365 Binary files /dev/null and b/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/loading2Lines.1.png differ diff --git a/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/partialLoaded.1.png b/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/partialLoaded.1.png index 469b5b1..805dea2 100644 Binary files a/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/partialLoaded.1.png and b/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/partialLoaded.1.png differ diff --git a/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/partialLoaded2Lines.1.png b/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/partialLoaded2Lines.1.png new file mode 100644 index 0000000..aed18e5 Binary files /dev/null and b/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/partialLoaded2Lines.1.png differ diff --git a/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/partialLoaded2LinesWithoutChevron.1.png b/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/partialLoaded2LinesWithoutChevron.1.png new file mode 100644 index 0000000..0279e05 Binary files /dev/null and b/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/partialLoaded2LinesWithoutChevron.1.png differ diff --git a/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/partialLoadedWithouChevron.1.png b/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/partialLoadedWithouChevron.1.png new file mode 100644 index 0000000..fec814f Binary files /dev/null and b/Projects/DesignSystem/Tests/Components/__Snapshots__/InfoCellSnapshotTests/partialLoadedWithouChevron.1.png differ diff --git a/Projects/Detail/Sources/Model/Asset.swift b/Projects/Detail/Sources/Model/Asset.swift index e1314a9..5989b33 100644 --- a/Projects/Detail/Sources/Model/Asset.swift +++ b/Projects/Detail/Sources/Model/Asset.swift @@ -15,6 +15,20 @@ struct Asset: Sendable, Equatable { let priceUsd: Double } +extension Asset { + var formattedPrice: String { + let formatter = NumberFormatter() + formatter.numberStyle = .currency + formatter.currencySymbol = "$" + formatter.minimumFractionDigits = 2 + formatter.maximumFractionDigits = 2 + formatter.usesGroupingSeparator = true + formatter.groupingSeparator = "." + formatter.decimalSeparator = "," + return formatter.string(from: NSNumber(value: priceUsd)) ?? "$ 0,00" + } +} + extension Asset: Codable { enum CodingKeys: String, CodingKey { case id, name, symbol diff --git a/Projects/Detail/Sources/View/AssetTableViewCell.swift b/Projects/Detail/Sources/View/AssetTableViewCell.swift deleted file mode 100644 index c613460..0000000 --- a/Projects/Detail/Sources/View/AssetTableViewCell.swift +++ /dev/null @@ -1,140 +0,0 @@ -// -// -// Created by Vitor Conceicao. -// -// github.com/vitor-rc1 -// -// - -import UIKit -import DesignSystem - -final class AssetTableViewCell: UITableViewCell { - static let reuseIdentifier = "AssetTableViewCell" - - private lazy var containerView: UIView = { - let view = UIView() - view.translatesAutoresizingMaskIntoConstraints = false - view.backgroundColor = .secondarySystemGroupedBackground - view.layer.cornerRadius = DSSpacings.medium - view.clipsToBounds = true - return view - }() - - private lazy var iconImageView: UIImageView = { - let imageView = UIImageView() - imageView.translatesAutoresizingMaskIntoConstraints = false - imageView.contentMode = .scaleAspectFit - imageView.clipsToBounds = true - imageView.tintColor = .systemGray3 - imageView.image = UIImage(systemName: "bitcoinsign.circle.fill") - return imageView - }() - - private lazy var nameLabel: UILabel = { - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - label.font = DSFonts.bodyRegular - label.textColor = .label - return label - }() - - private lazy var priceLabel: UILabel = { - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - label.font = DSFonts.subheadlineRegular - label.textColor = .systemGreen - return label - }() - - private lazy var textStackView: UIStackView = { - let stackView = UIStackView(arrangedSubviews: [nameLabel, priceLabel]) - stackView.translatesAutoresizingMaskIntoConstraints = false - stackView.axis = .vertical - stackView.spacing = DSSpacings.xSmall - return stackView - }() - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - setupView() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func configure(with asset: Asset) { - nameLabel.text = asset.name - priceLabel.text = formatPrice(asset.priceUsd) - } - - private func formatPrice(_ value: Double) -> String { - let formatter = NumberFormatter() - formatter.numberStyle = .currency - formatter.currencySymbol = "$" - formatter.minimumFractionDigits = 2 - formatter.maximumFractionDigits = 2 - formatter.usesGroupingSeparator = true - formatter.groupingSeparator = "." - formatter.decimalSeparator = "," - - return formatter.string(from: NSNumber(value: value)) ?? "$ 0,00" - } -} - -extension AssetTableViewCell: ViewCode { - func buildViewHierarch() { - contentView.addSubview(containerView) - containerView.addSubview(iconImageView) - containerView.addSubview(textStackView) - } - - func setUpConstraints() { - let horizontalPadding = DSSpacings.large - let verticalPadding = DSSpacings.small - - NSLayoutConstraint.activate([ - containerView.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, - constant: verticalPadding), - containerView.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, - constant: -verticalPadding), - containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, - constant: horizontalPadding), - containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, - constant: -horizontalPadding), - containerView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - - iconImageView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, - constant: horizontalPadding), - iconImageView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor), - iconImageView.widthAnchor.constraint(equalToConstant: 42), - iconImageView.heightAnchor.constraint(equalToConstant: 42), - - textStackView.leadingAnchor.constraint(equalTo: iconImageView.trailingAnchor, - constant: horizontalPadding), - textStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, - constant: -horizontalPadding), - textStackView.topAnchor.constraint(equalTo: containerView.topAnchor, - constant: DSSpacings.small), - textStackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, - constant: -DSSpacings.medium) - ]) - } - - func additionalConfiguration() { - selectionStyle = .none - contentView.backgroundColor = .clear - } -} - -#if DEBUG -import SwiftUI - -@available(iOS 17.0, *) -#Preview { - let view = AssetTableViewCell() - view.configure(with: .init(id: 1, name: "opa", symbol: "OPa", priceUsd: 123.4)) - return view -} -#endif diff --git a/Projects/Detail/Sources/View/DetailViewController.swift b/Projects/Detail/Sources/View/DetailViewController.swift index 885fab8..8db7c01 100644 --- a/Projects/Detail/Sources/View/DetailViewController.swift +++ b/Projects/Detail/Sources/View/DetailViewController.swift @@ -22,7 +22,7 @@ final class DetailViewController: UIViewController { private lazy var tableView: UITableView = { let tableView = UITableView(frame: .zero, style: .plain) tableView.translatesAutoresizingMaskIntoConstraints = false - tableView.register(AssetTableViewCell.self, forCellReuseIdentifier: AssetTableViewCell.reuseIdentifier) + tableView.register(InfoCell.self, forCellReuseIdentifier: InfoCell.identifier) tableView.delegate = self tableView.dataSource = self tableView.separatorStyle = .none @@ -105,13 +105,20 @@ extension DetailViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell( - withIdentifier: AssetTableViewCell.reuseIdentifier, + withIdentifier: InfoCell.identifier, for: indexPath - ) as? AssetTableViewCell else { + ) as? InfoCell else { return UITableViewCell() } - cell.configure(with: viewModel.assets[indexPath.row]) + let asset = viewModel.assets[indexPath.row] + cell.configure(state: .loaded(.init( + url: "", + title: asset.name, + subtitle: asset.formattedPrice, + defaultImage: UIImage(systemName: "bitcoinsign.circle.fill"), + hiddenChevron: true + ))) return cell } } diff --git a/Projects/Home/Sources/View/HomeViewController.swift b/Projects/Home/Sources/View/HomeViewController.swift index c0bf436..d938108 100644 --- a/Projects/Home/Sources/View/HomeViewController.swift +++ b/Projects/Home/Sources/View/HomeViewController.swift @@ -136,11 +136,11 @@ extension HomeViewController: UITableViewDataSource, UITableViewDelegate { switch viewModel.state { case .loading: - cell.configure(state: .loading) + cell.configure(state: .loading()) case .loaded: let item = viewModel.item(at: indexPath.row) if item.isLoadingDetails { - cell.configure(state: .partialLoaded(item.name)) + cell.configure(state: .partialLoaded(.init(title: item.name))) } else { let volPrice = "Vol: \(viewModel.formatPrice(item.spotVolumeUsd ?? 0.0))" let dateLaunched = "Date launched: \(viewModel.formatDate(item.dateLaunched))"