Skip to content

Commit f98a6e0

Browse files
author
Alexey Naumov
committed
Revamp the project
1 parent 94193d9 commit f98a6e0

File tree

118 files changed

+3166
-5804
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

118 files changed

+3166
-5804
lines changed

CountriesSwiftUI.xcodeproj/project.pbxproj

Lines changed: 173 additions & 725 deletions
Large diffs are not rendered by default.

CountriesSwiftUI.xcodeproj/xcshareddata/xcschemes/CountriesSwiftUI.xcscheme

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1430"
4-
version = "1.3">
3+
LastUpgradeVersion = "1610"
4+
version = "1.7">
55
<BuildAction
66
parallelizeBuildables = "YES"
7-
buildImplicitDependencies = "YES">
7+
buildImplicitDependencies = "YES"
8+
buildArchitectures = "Automatic">
89
<BuildActionEntries>
910
<BuildActionEntry
1011
buildForTesting = "YES"
@@ -14,7 +15,7 @@
1415
buildForAnalyzing = "YES">
1516
<BuildableReference
1617
BuildableIdentifier = "primary"
17-
BlueprintIdentifier = "F64495E02360D66400C9BB1F"
18+
BlueprintIdentifier = "488734162CDCA2DB00B400A3"
1819
BuildableName = "CountriesSwiftUI.app"
1920
BlueprintName = "CountriesSwiftUI"
2021
ReferencedContainer = "container:CountriesSwiftUI.xcodeproj">
@@ -27,23 +28,14 @@
2728
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
2829
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
2930
shouldUseLaunchSchemeArgsEnv = "YES"
30-
codeCoverageEnabled = "YES"
31-
onlyGenerateCoverageForSpecifiedTargets = "YES">
32-
<CodeCoverageTargets>
33-
<BuildableReference
34-
BuildableIdentifier = "primary"
35-
BlueprintIdentifier = "F64495E02360D66400C9BB1F"
36-
BuildableName = "CountriesSwiftUI.app"
37-
BlueprintName = "CountriesSwiftUI"
38-
ReferencedContainer = "container:CountriesSwiftUI.xcodeproj">
39-
</BuildableReference>
40-
</CodeCoverageTargets>
31+
shouldAutocreateTestPlan = "YES">
4132
<Testables>
4233
<TestableReference
43-
skipped = "NO">
34+
skipped = "NO"
35+
parallelizable = "YES">
4436
<BuildableReference
4537
BuildableIdentifier = "primary"
46-
BlueprintIdentifier = "F67833D12369CCBD0065272F"
38+
BlueprintIdentifier = "488734282CDCA2DD00B400A3"
4739
BuildableName = "UnitTests.xctest"
4840
BlueprintName = "UnitTests"
4941
ReferencedContainer = "container:CountriesSwiftUI.xcodeproj">
@@ -65,7 +57,7 @@
6557
runnableDebuggingMode = "0">
6658
<BuildableReference
6759
BuildableIdentifier = "primary"
68-
BlueprintIdentifier = "F64495E02360D66400C9BB1F"
60+
BlueprintIdentifier = "488734162CDCA2DB00B400A3"
6961
BuildableName = "CountriesSwiftUI.app"
7062
BlueprintName = "CountriesSwiftUI"
7163
ReferencedContainer = "container:CountriesSwiftUI.xcodeproj">
@@ -82,7 +74,7 @@
8274
runnableDebuggingMode = "0">
8375
<BuildableReference
8476
BuildableIdentifier = "primary"
85-
BlueprintIdentifier = "F64495E02360D66400C9BB1F"
77+
BlueprintIdentifier = "488734162CDCA2DB00B400A3"
8678
BuildableName = "CountriesSwiftUI.app"
8779
BlueprintName = "CountriesSwiftUI"
8880
ReferencedContainer = "container:CountriesSwiftUI.xcodeproj">

CountriesSwiftUI/Core/App.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// CountriesApp.swift
3+
// CountriesSwiftUI
4+
//
5+
// Created by Alexey on 7/11/24.
6+
// Copyright © 2024 Alexey Naumov. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
import EnvironmentOverrides
11+
12+
@main
13+
struct MainApp: App {
14+
15+
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
16+
17+
var body: some Scene {
18+
WindowGroup {
19+
appDelegate.rootView
20+
}
21+
}
22+
}
23+
24+
extension AppEnvironment {
25+
var rootView: some View {
26+
VStack {
27+
if isRunningTests {
28+
Text("Running unit tests")
29+
} else {
30+
CountriesList()
31+
.modifier(RootViewAppearance())
32+
.modelContainer(modelContainer)
33+
.attachEnvironmentOverrides(onChange: onChangeHandler)
34+
.inject(diContainer)
35+
if modelContainer.isStub {
36+
Text("⚠️ There is an issue with local database")
37+
.font(.caption2)
38+
}
39+
}
40+
}
41+
}
42+
43+
private var onChangeHandler: (EnvironmentValues.Diff) -> Void {
44+
return { diff in
45+
if !diff.isDisjoint(with: [.locale, .sizeCategory]) {
46+
self.diContainer.appState[\.routing] = AppState.ViewRouting()
47+
}
48+
}
49+
}
50+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//
2+
// AppDelegate.swift
3+
// CountriesSwiftUI
4+
//
5+
// Created by Alexey Naumov on 23.10.2019.
6+
// Copyright © 2019 Alexey Naumov. All rights reserved.
7+
//
8+
9+
import UIKit
10+
import SwiftUI
11+
import Combine
12+
import Foundation
13+
14+
@MainActor
15+
final class AppDelegate: UIResponder, UIApplicationDelegate {
16+
17+
private lazy var environment = AppEnvironment.bootstrap()
18+
private var systemEventsHandler: SystemEventsHandler { environment.systemEventsHandler }
19+
20+
var rootView: some View {
21+
environment.rootView
22+
}
23+
24+
func application(_ application: UIApplication, didFinishLaunchingWithOptions
25+
launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
26+
return true
27+
}
28+
29+
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
30+
let config: UISceneConfiguration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
31+
config.delegateClass = SceneDelegate.self
32+
SceneDelegate.register(systemEventsHandler)
33+
return config
34+
}
35+
36+
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
37+
systemEventsHandler.handlePushRegistration(result: .success(deviceToken))
38+
}
39+
40+
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
41+
systemEventsHandler.handlePushRegistration(result: .failure(error))
42+
}
43+
44+
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async -> UIBackgroundFetchResult {
45+
return await systemEventsHandler
46+
.appDidReceiveRemoteNotification(payload: userInfo)
47+
}
48+
}
49+
50+
// MARK: - SceneDelegate
51+
52+
@MainActor
53+
final class SceneDelegate: UIResponder, UIWindowSceneDelegate, ObservableObject {
54+
55+
private static var systemEventsHandler: SystemEventsHandler?
56+
private var systemEventsHandler: SystemEventsHandler? { Self.systemEventsHandler }
57+
58+
static func register(_ systemEventsHandler: SystemEventsHandler?) {
59+
Self.systemEventsHandler = systemEventsHandler
60+
}
61+
62+
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
63+
systemEventsHandler?.sceneOpenURLContexts(URLContexts)
64+
}
65+
66+
func sceneDidBecomeActive(_ scene: UIScene) {
67+
systemEventsHandler?.sceneDidBecomeActive()
68+
}
69+
70+
func sceneWillResignActive(_ scene: UIScene) {
71+
systemEventsHandler?.sceneWillResignActive()
72+
}
73+
}

CountriesSwiftUI/Injected/AppState.swift renamed to CountriesSwiftUI/Core/AppState.swift

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,11 @@ import SwiftUI
1010
import Combine
1111

1212
struct AppState: Equatable {
13-
var userData = UserData()
1413
var routing = ViewRouting()
1514
var system = System()
1615
var permissions = Permissions()
1716
}
1817

19-
extension AppState {
20-
struct UserData: Equatable {
21-
/*
22-
The list of countries (Loadable<[Country]>) used to be stored here.
23-
It was removed for performing countries' search by name inside a database,
24-
which made the resulting variable used locally by just one screen (CountriesList)
25-
Otherwise, the list of countries could have remained here, available for the entire app.
26-
*/
27-
}
28-
}
29-
3018
extension AppState {
3119
struct ViewRouting: Equatable {
3220
var countriesList = CountriesList.Routing()
@@ -45,7 +33,7 @@ extension AppState {
4533
struct Permissions: Equatable {
4634
var push: Permission.Status = .unknown
4735
}
48-
36+
4937
static func permissionKeyPath(for permission: Permission) -> WritableKeyPath<AppState, Permission.Status> {
5038
let pathToPermissions = \AppState.permissions
5139
switch permission {
@@ -56,18 +44,7 @@ extension AppState {
5644
}
5745

5846
func == (lhs: AppState, rhs: AppState) -> Bool {
59-
return lhs.userData == rhs.userData &&
60-
lhs.routing == rhs.routing &&
61-
lhs.system == rhs.system &&
62-
lhs.permissions == rhs.permissions
63-
}
64-
65-
#if DEBUG
66-
extension AppState {
67-
static var preview: AppState {
68-
var state = AppState()
69-
state.system.isActive = true
70-
return state
71-
}
47+
return lhs.routing == rhs.routing
48+
&& lhs.system == rhs.system
49+
&& lhs.permissions == rhs.permissions
7250
}
73-
#endif

CountriesSwiftUI/System/DeepLinksHandler.swift renamed to CountriesSwiftUI/Core/DeepLinksHandler.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import Foundation
1010

1111
enum DeepLink: Equatable {
1212

13-
case showCountryFlag(alpha3Code: Country.Code)
14-
13+
case showCountryFlag(alpha3Code: String)
14+
1515
init?(url: URL) {
1616
guard
1717
let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
@@ -20,7 +20,7 @@ enum DeepLink: Equatable {
2020
else { return nil }
2121
if let item = query.first(where: { $0.name == "alpha3code" }),
2222
let alpha3Code = item.value {
23-
self = .showCountryFlag(alpha3Code: Country.Code(alpha3Code))
23+
self = .showCountryFlag(alpha3Code: alpha3Code)
2424
return
2525
}
2626
return nil
@@ -29,6 +29,7 @@ enum DeepLink: Equatable {
2929

3030
// MARK: - DeepLinksHandler
3131

32+
@MainActor
3233
protocol DeepLinksHandler {
3334
func open(deepLink: DeepLink)
3435
}
@@ -46,7 +47,7 @@ struct RealDeepLinksHandler: DeepLinksHandler {
4647
case let .showCountryFlag(alpha3Code):
4748
let routeToDestination = {
4849
self.container.appState.bulkUpdate {
49-
$0.routing.countriesList.countryDetails = alpha3Code
50+
$0.routing.countriesList.countryCode = alpha3Code
5051
$0.routing.countryDetails.detailsSheet = true
5152
}
5253
}
@@ -58,7 +59,8 @@ struct RealDeepLinksHandler: DeepLinksHandler {
5859
let defaultRouting = AppState.ViewRouting()
5960
if container.appState.value.routing != defaultRouting {
6061
self.container.appState[\.routing] = defaultRouting
61-
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5, execute: routeToDestination)
62+
let delay: DispatchTime = .now() + (ProcessInfo.processInfo.isRunningTests ? 0 : 1.5)
63+
DispatchQueue.main.asyncAfter(deadline: delay, execute: routeToDestination)
6264
} else {
6365
routeToDestination()
6466
}

CountriesSwiftUI/System/PushNotificationsHandler.swift renamed to CountriesSwiftUI/Core/PushNotificationsHandler.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,14 @@ extension RealPushNotificationsHandler: UNUserNotificationCenterDelegate {
4040
}
4141

4242
func handleNotification(userInfo: [AnyHashable: Any], completionHandler: @escaping () -> Void) {
43-
guard let payload = userInfo["aps"] as? NotificationPayload,
44-
let countryCode = payload["country"] as? Country.Code else {
43+
guard let payload = userInfo["aps"] as? [AnyHashable: Any],
44+
let countryCode = payload["country"] as? String else {
4545
completionHandler()
4646
return
4747
}
48-
deepLinksHandler.open(deepLink: .showCountryFlag(alpha3Code: countryCode))
49-
completionHandler()
48+
Task { @MainActor in
49+
deepLinksHandler.open(deepLink: .showCountryFlag(alpha3Code: countryCode))
50+
completionHandler()
51+
}
5052
}
5153
}

0 commit comments

Comments
 (0)