Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions DebugScreen.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@
objects = {

/* Begin PBXBuildFile section */
400C6A2B2F3F67C500678284 /* MenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 400C6A2A2F3F67C500678284 /* MenuItem.swift */; };
400C6A2D2F3F69A700678284 /* NestedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 400C6A2C2F3F69A700678284 /* NestedScreen.swift */; };
400C6A322F3F700200678284 /* DebugScreenMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 400C6A312F3F700100678284 /* DebugScreenMenuItem.swift */; };
400C6A342F3F700400678284 /* DebugScreenNestedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 400C6A332F3F700300678284 /* DebugScreenNestedScreen.swift */; };
400C6A362F3F700600678284 /* MenuItemsSectionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 400C6A352F3F700500678284 /* MenuItemsSectionBuilder.swift */; };
400C6A382F3F700800678284 /* NestedScreensSectionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 400C6A372F3F700700678284 /* NestedScreensSectionBuilder.swift */; };
9054BA4A2A5C6CCA0009D70D /* BaseNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9054BA492A5C6CCA0009D70D /* BaseNavigationController.swift */; };
90985F5C2A5C58980066790A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B2E91F9A2A4C2308000E3E45 /* LaunchScreen.storyboard */; };
90C0C14C2F45CC4300D3C438 /* MainScreenType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90C0C14B2F45CC3D00D3C438 /* MainScreenType.swift */; };
A4301A65285A462F006B77C0 /* DebugScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A43019E6285A3137006B77C0 /* DebugScreenTests.swift */; };
A47BDB42286A1249002326B8 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A47BDB38286A1249002326B8 /* Colors.xcassets */; };
A47BDB43286A1249002326B8 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = A47BDB39286A1249002326B8 /* Colors.swift */; };
Expand Down Expand Up @@ -174,7 +181,14 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
400C6A2A2F3F67C500678284 /* MenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItem.swift; sourceTree = "<group>"; };
400C6A2C2F3F69A700678284 /* NestedScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedScreen.swift; sourceTree = "<group>"; };
400C6A312F3F700100678284 /* DebugScreenMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugScreenMenuItem.swift; sourceTree = "<group>"; };
400C6A332F3F700300678284 /* DebugScreenNestedScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugScreenNestedScreen.swift; sourceTree = "<group>"; };
400C6A352F3F700500678284 /* MenuItemsSectionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemsSectionBuilder.swift; sourceTree = "<group>"; };
400C6A372F3F700700678284 /* NestedScreensSectionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedScreensSectionBuilder.swift; sourceTree = "<group>"; };
9054BA492A5C6CCA0009D70D /* BaseNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNavigationController.swift; sourceTree = "<group>"; };
90C0C14B2F45CC3D00D3C438 /* MainScreenType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScreenType.swift; sourceTree = "<group>"; };
A43019E6285A3137006B77C0 /* DebugScreenTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugScreenTests.swift; sourceTree = "<group>"; };
A47BDB38286A1249002326B8 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = "<group>"; };
A47BDB39286A1249002326B8 /* Colors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -565,6 +579,7 @@
A4BDF4B628512FAA00651C61 /* Configurator */ = {
isa = PBXGroup;
children = (
90C0C14B2F45CC3D00D3C438 /* MainScreenType.swift */,
A4BDF4B728512FAA00651C61 /* MainModuleConfigurator.swift */,
);
path = Configurator;
Expand Down Expand Up @@ -710,6 +725,8 @@
B28F97E92A271FC00089C032 /* CopiedText.swift */,
B28F97E72A271FAF0089C032 /* FeatureToggle.swift */,
B28F97EB2A271FF30089C032 /* SelectionList.swift */,
400C6A2A2F3F67C500678284 /* MenuItem.swift */,
400C6A2C2F3F69A700678284 /* NestedScreen.swift */,
);
path = Content;
sourceTree = "<group>";
Expand Down Expand Up @@ -956,6 +973,8 @@
B2E91F772A4C2305000E3E45 /* ActionsSectionBuilder.swift */,
B2E91F792A4C2305000E3E45 /* CopiedTextSectionBuilder.swift */,
B2B1D4502A52FC3000CC5A11 /* InfoTablesSectionBuilder.swift */,
400C6A352F3F700500678284 /* MenuItemsSectionBuilder.swift */,
400C6A372F3F700700678284 /* NestedScreensSectionBuilder.swift */,
B2E91F7A2A4C2305000E3E45 /* ServerSelectionSectionBuilder.swift */,
B2E91F782A4C2305000E3E45 /* TogglesSectionBuilder.swift */,
);
Expand All @@ -969,6 +988,8 @@
B2E91F7D2A4C2305000E3E45 /* CopiedTextItem.swift */,
B2E91F7E2A4C2305000E3E45 /* DebugScreenActionList.swift */,
B2E91F7F2A4C2305000E3E45 /* DebugScreenAction.swift */,
400C6A312F3F700100678284 /* DebugScreenMenuItem.swift */,
400C6A332F3F700300678284 /* DebugScreenNestedScreen.swift */,
B2E91F802A4C2305000E3E45 /* ServersSelectionList.swift */,
);
path = Models;
Expand Down Expand Up @@ -1359,9 +1380,11 @@
B28F97F92A27313E0089C032 /* UIImage.swift in Sources */,
A47BDB43286A1249002326B8 /* Colors.swift in Sources */,
A4BDF49828512F7700651C61 /* UIApplication.swift in Sources */,
90C0C14C2F45CC4300D3C438 /* MainScreenType.swift in Sources */,
A4BDF4C528512FAA00651C61 /* MainViewController.swift in Sources */,
B2610E242A16DCC300509FA1 /* SimpleAlertModuleConfigurator.swift in Sources */,
B28F97E42A271F980089C032 /* Action.swift in Sources */,
400C6A2D2F3F69A700678284 /* NestedScreen.swift in Sources */,
9054BA4A2A5C6CCA0009D70D /* BaseNavigationController.swift in Sources */,
B2B1D4442A52DB7F00CC5A11 /* InfoTableModuleInput.swift in Sources */,
B237FF652A2913DF0095FA33 /* LoggerSectionComponent.swift in Sources */,
Expand All @@ -1375,6 +1398,7 @@
A4BDF4C728512FAA00651C61 /* MainModuleConfigurator.swift in Sources */,
B2C65D122A4571E0004DC80D /* UITableViewCell.swift in Sources */,
A4BDF49328512F7700651C61 /* Array.swift in Sources */,
400C6A2B2F3F67C500678284 /* MenuItem.swift in Sources */,
B2B1D4492A52DBA400CC5A11 /* InfoTableViewInput.swift in Sources */,
A4BDF49428512F7700651C61 /* NSObject.swift in Sources */,
A4D337562863A7AA002328F2 /* TableSection.swift in Sources */,
Expand Down Expand Up @@ -1472,6 +1496,10 @@
B2E91FA42A4C2309000E3E45 /* UserDefaultsFeatureToggle.swift in Sources */,
B2E91FB22A4C2309000E3E45 /* UIImage.swift in Sources */,
B2E91FA72A4C2309000E3E45 /* DebugScreenAction.swift in Sources */,
400C6A322F3F700200678284 /* DebugScreenMenuItem.swift in Sources */,
400C6A342F3F700400678284 /* DebugScreenNestedScreen.swift in Sources */,
400C6A362F3F700600678284 /* MenuItemsSectionBuilder.swift in Sources */,
400C6A382F3F700800678284 /* NestedScreensSectionBuilder.swift in Sources */,
B2E91FB12A4C2309000E3E45 /* UIButton.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
4 changes: 4 additions & 0 deletions DebugScreen/Common/Models/Common/MainTableBlock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ public enum MainTableBlock {
case selectionList(model: SelectionList)
/// Block with toggle.
case toggle(model: FeatureToggle)
/// Block with a custom menu item that performs an action on tap.
case menuItem(model: MenuItem)
/// Block that opens a nested section screen with its own table sections.
case nestedScreen(model: NestedScreen)
}
15 changes: 15 additions & 0 deletions DebugScreen/Common/Protocols/Content/MenuItem.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// MenuItem.swift
// DebugScreen
//
// Created by Aleksandr Potemkin on 13.02.2026.
//

public protocol MenuItem {
/// Item title text (supports multiline).
var title: String { get }
/// Whether to show a disclosure indicator arrow.
var showsDisclosure: Bool { get }
/// Action performed on tap.
var block: (() -> Void)? { get }
}
13 changes: 13 additions & 0 deletions DebugScreen/Common/Protocols/Content/NestedScreen.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// NestedScreen.swift
// DebugScreen
//
// Created by Aleksandr Potemkin on 13.02.2026.
//

public protocol NestedScreen {
/// Screen title displayed in the navigation bar.
var title: String { get }
/// Sections — same ``TableSection`` items as on the main screen.
var sections: [TableSection] { get }
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ private extension PlainTextCell {
}

func configureContainer() {
accessoryType = .disclosureIndicator
backgroundColor = DebugScreenConfiguration.shared.colorScheme.backgroundColor
}

Expand Down
31 changes: 25 additions & 6 deletions DebugScreen/Flows/DebugScreenCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ final class DebugScreenCoordinator: BaseCoordinator {
// MARK: - Methods

override func start() {
showMainScreen()
showMainScreen(type: .root)
}

func open(module: ModuleType) {
Expand All @@ -43,8 +43,15 @@ final class DebugScreenCoordinator: BaseCoordinator {

private extension DebugScreenCoordinator {

func showMainScreen() {
let (view, output) = MainModuleConfigurator().configure()
func showMainScreen(type: MainScreenType) {
let (view, output): MainModuleComponents
switch type {
case .root:
(view, output) = MainModuleConfigurator().configureMain()
case .nested(let model):
(view, output) = MainModuleConfigurator().configureNestedModule(model: model)
}

output.onActionListShow = { [weak self] model in
self?.showActionList(model: model)
}
Expand All @@ -54,13 +61,25 @@ private extension DebugScreenCoordinator {
output.onInfoTableShow = { [weak self] model in
self?.showInfoTable(with: model)
}
output.onNestedScreenShow = { [weak self] model in
self?.showMainScreen(type: .nested(model: model))
}
output.didModuleClosed = { [weak self] in
self?.router.dismissModule()
}
output.didModuleDismissed = { [weak self] in
self?.completionHandler?()

if case .root = type {
output.didModuleDismissed = { [weak self] in
self?.completionHandler?()
}
}

switch type {
case .root:
router.present(view)
case .nested(_):
router.push(view)
}
router.present(view)
}

func showActionList(model: ActionList) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,33 @@ final class MainModuleConfigurator {

// MARK: - Methods

func configure() -> MainModuleComponents {
let viewController = UIViewController.instantiate(ofType: MainViewController.self)
let presenter = MainModulePresenter()
func configureMain() -> MainModuleComponents {
let view = UIViewController.instantiate(ofType: MainViewController.self)
let presenter = MainModulePresenter(
title: L10n.MainViewController.debugTitle,
sections: DebugScreenConfiguration.shared.sections
)

presenter.view = viewController
viewController.output = presenter
presenter.view = view
view.output = presenter

let navController = BaseNavigationController(rootViewController: viewController)
let navController = BaseNavigationController(rootViewController: view)
navController.modalPresentationStyle = .overFullScreen

return (navController, presenter)
}

func configureNestedModule(model: NestedScreen) -> MainModuleComponents {
let view = UIViewController.instantiate(ofType: MainViewController.self)
let presenter = MainModulePresenter(
title: model.title,
sections: model.sections
)

presenter.view = view
view.output = presenter

return (view, presenter)
}

}
13 changes: 13 additions & 0 deletions DebugScreen/Flows/MainScreen/Configurator/MainScreenType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// MainScreenType.swift
// DebugScreen
//
// Created by chausov on 18.02.2026.
//

enum MainScreenType {
/// Main debug screen
case root
/// Nested debug screen
case nested(model: NestedScreen)
}
2 changes: 2 additions & 0 deletions DebugScreen/Flows/MainScreen/Presenter/MainModuleOutput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ protocol MainModuleOutput: AnyObject {
var onAlertShow: ((AlertModel) -> Void)? { get set }
/// Call then show information table screen
var onInfoTableShow: ((InfoTableModel) -> Void)? { get set }
/// Called when a section screen needs to be shown
var onNestedScreenShow: ((NestedScreen) -> Void)? { get set }
/// Call then close module
var didModuleClosed: (() -> Void)? { get set }
/// Call after module deinit from memory
Expand Down
22 changes: 21 additions & 1 deletion DebugScreen/Flows/MainScreen/Presenter/MainModulePresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,26 @@ final class MainModulePresenter: MainModuleOutput {
var onActionListShow: ((ActionList) -> Void)?
var onAlertShow: ((AlertModel) -> Void)?
var onInfoTableShow: ((InfoTableModel) -> Void)?
var onNestedScreenShow: ((NestedScreen) -> Void)?
var didModuleClosed: (() -> Void)?
var didModuleDismissed: (() -> Void)?

// MARK: - Properties

weak var view: MainViewInput?

// MARK: - Private Properties

private let title: String
private let sections: [TableSection]

// MARK: - Initialization

init(title: String, sections: [TableSection]) {
self.title = title
self.sections = sections
}

// MARK: - Deinitialization

deinit {
Expand All @@ -39,7 +52,10 @@ extension MainModulePresenter: MainModuleInput { }
extension MainModulePresenter: MainViewOutput {

func viewLoaded() {
view?.setupInitialState(sections: DebugScreenConfiguration.shared.sections)
view?.setupInitialState(
title: title,
sections: sections
)
}

func didTapActionList(model: ActionList) {
Expand All @@ -56,6 +72,10 @@ extension MainModulePresenter: MainViewOutput {
debugPrint("✅ \(model.title) copied to clipboard")
}

func didTapNestedScreen(model: NestedScreen) {
onNestedScreenShow?(model)
}

func didTapCloseButton() {
didModuleClosed?()
}
Expand Down
21 changes: 19 additions & 2 deletions DebugScreen/Flows/MainScreen/View/Adapter/MainAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ final class MainAdapter: NSObject {
var onOpenActionList: ((ActionList) -> Void)?
var onSelectableTextTap: ((CopiedText) -> Void)?
var onOpenInfoTable: ((InfoTableModel) -> Void)?
var onOpenNestedScreen: ((NestedScreen) -> Void)?

// MARK: - Private Properties

Expand Down Expand Up @@ -81,11 +82,23 @@ extension MainAdapter: UITableViewDataSource {
case .toggle(let model):
return configureSwitcherCell(tableView, indexPath: indexPath, model: model)
case .copiedText(let model):
return configurePlainTextCell(tableView, indexPath: indexPath, title: model.title)
let cell = configurePlainTextCell(tableView, indexPath: indexPath, title: model.title)
cell.accessoryType = .disclosureIndicator
return cell
case .infoTable(let model):
return configurePlainTextCell(tableView, indexPath: indexPath, title: model.header)
let cell = configurePlainTextCell(tableView, indexPath: indexPath, title: model.header)
cell.accessoryType = .disclosureIndicator
return cell
case .selectionList(let model):
return configureSelectorCell(tableView, indexPath: indexPath, model: model)
case .menuItem(let model):
let cell = configurePlainTextCell(tableView, indexPath: indexPath, title: model.title)
cell.accessoryType = model.showsDisclosure ? .disclosureIndicator : .none
return cell
case .nestedScreen(let model):
let cell = configurePlainTextCell(tableView, indexPath: indexPath, title: model.title)
cell.accessoryType = .disclosureIndicator
return cell
}
}

Expand Down Expand Up @@ -129,6 +142,10 @@ extension MainAdapter: UITableViewDelegate {
onSelectableTextTap?(model)
case .infoTable(let model):
onOpenInfoTable?(model)
case .menuItem(let model):
model.block?()
case .nestedScreen(let model):
onOpenNestedScreen?(model)
default:
return
}
Expand Down
15 changes: 9 additions & 6 deletions DebugScreen/Flows/MainScreen/View/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ final class MainViewController: UIViewController {

extension MainViewController: MainViewInput {

func setupInitialState(sections: [TableSection]) {
configureAppearance()
func setupInitialState(title: String, sections: [TableSection]) {
configureAppearance(title: title)
adapter?.fill(with: sections)
}

Expand All @@ -45,15 +45,15 @@ extension MainViewController: MainViewInput {

private extension MainViewController {

func configureAppearance() {
func configureAppearance(title: String) {
view.backgroundColor = DebugScreenConfiguration.shared.colorScheme.backgroundColor
configureNavigationBar()
configureNavigationBar(title: title)
configureTableView()
configureAdapter()
}

func configureNavigationBar() {
navigationItem.title = L10n.MainViewController.debugTitle
func configureNavigationBar(title: String) {
navigationItem.title = title
configureCloseButton()
}

Expand All @@ -78,6 +78,9 @@ private extension MainViewController {
adapter?.onSelectableTextTap = { [weak self] model in
self?.output?.didTapSelectableText(model: model)
}
adapter?.onOpenNestedScreen = { [weak self] model in
self?.output?.didTapNestedScreen(model: model)
}
}

func configureCloseButton() {
Expand Down
Loading