diff --git a/FakeNFT.xcodeproj/project.pbxproj b/FakeNFT.xcodeproj/project.pbxproj index b8ab3cee98..0363aa941e 100644 --- a/FakeNFT.xcodeproj/project.pbxproj +++ b/FakeNFT.xcodeproj/project.pbxproj @@ -29,6 +29,10 @@ 5019A4CE2DE1E899009A5AF8 /* FakeNftApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5019A4CD2DE1E899009A5AF8 /* FakeNftApp.swift */; }; 5019A4D02DE1E8E0009A5AF8 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5019A4CF2DE1E8E0009A5AF8 /* ContentView.swift */; }; 558E39E72C68CE0A00FB86AC /* NftService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 558E39E62C68CE0900FB86AC /* NftService.swift */; }; + 793BCC8E2DEEF05000DF1252 /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 793BCC8D2DEEF05000DF1252 /* WebView.swift */; }; + 793BCC902DEEF06800DF1252 /* WebViewRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 793BCC8F2DEEF06800DF1252 /* WebViewRepresentable.swift */; }; + 793BCC922DEEF46600DF1252 /* Statistics.md in Resources */ = {isa = PBXBuildFile; fileRef = 793BCC912DEEF46600DF1252 /* Statistics.md */; }; + 793BCC942DEEF47200DF1252 /* CartAnisimov.md in Resources */ = {isa = PBXBuildFile; fileRef = 793BCC932DEEF47200DF1252 /* CartAnisimov.md */; }; 79D0C7532DE396AC00D53241 /* Tab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79D0C7522DE396AC00D53241 /* Tab.swift */; }; 79D0C7562DE3973200D53241 /* TabBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79D0C7552DE3973200D53241 /* TabBarView.swift */; }; 79D0C7592DE39C3800D53241 /* NavigationBarStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79D0C7582DE39C3800D53241 /* NavigationBarStyle.swift */; }; @@ -78,6 +82,10 @@ 5019A4CD2DE1E899009A5AF8 /* FakeNftApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeNftApp.swift; sourceTree = ""; }; 5019A4CF2DE1E8E0009A5AF8 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 558E39E62C68CE0900FB86AC /* NftService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftService.swift; sourceTree = ""; }; + 793BCC8D2DEEF05000DF1252 /* WebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = ""; }; + 793BCC8F2DEEF06800DF1252 /* WebViewRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewRepresentable.swift; sourceTree = ""; }; + 793BCC912DEEF46600DF1252 /* Statistics.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Statistics.md; sourceTree = ""; }; + 793BCC932DEEF47200DF1252 /* CartAnisimov.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CartAnisimov.md; sourceTree = ""; }; 79D0C7522DE396AC00D53241 /* Tab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tab.swift; sourceTree = ""; }; 79D0C7552DE3973200D53241 /* TabBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarView.swift; sourceTree = ""; }; 79D0C7582DE39C3800D53241 /* NavigationBarStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarStyle.swift; sourceTree = ""; }; @@ -115,6 +123,7 @@ 0CF2C2D92A783C1600FDC837 /* Common */ = { isa = PBXGroup; children = ( + 793BCC8C2DEEF03300DF1252 /* WebView */, 79D0C75B2DE39F3900D53241 /* LoadingView */, 0CF2C2DE2A784C4C00FDC837 /* Protocols */, ); @@ -269,9 +278,20 @@ path = Requests; sourceTree = ""; }; + 793BCC8C2DEEF03300DF1252 /* WebView */ = { + isa = PBXGroup; + children = ( + 793BCC8D2DEEF05000DF1252 /* WebView.swift */, + 793BCC8F2DEEF06800DF1252 /* WebViewRepresentable.swift */, + ); + path = WebView; + sourceTree = ""; + }; 79D0C74F2DE393F600D53241 /* Docs */ = { isa = PBXGroup; children = ( + 793BCC912DEEF46600DF1252 /* Statistics.md */, + 793BCC932DEEF47200DF1252 /* CartAnisimov.md */, ); path = Docs; sourceTree = ""; @@ -446,6 +466,8 @@ files = ( E1CD40DC2A96BECC00BE7FE8 /* Localizable.strings in Resources */, 3F6806A029CBBAF200B4F915 /* Assets.xcassets in Resources */, + 793BCC942DEEF47200DF1252 /* CartAnisimov.md in Resources */, + 793BCC922DEEF46600DF1252 /* Statistics.md in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -482,6 +504,7 @@ 3F6806D529CBBEC700B4F915 /* NetworkTask.swift in Sources */, 558E39E72C68CE0A00FB86AC /* NftService.swift in Sources */, 0C79EE6C2A76DE2E00EE90EA /* ServicesAssemly.swift in Sources */, + 793BCC902DEEF06800DF1252 /* WebViewRepresentable.swift in Sources */, 79D0C7532DE396AC00D53241 /* Tab.swift in Sources */, 0CFCB74E2A7817DC0009A829 /* NftStorage.swift in Sources */, 3FC8C39129D2453B0081F015 /* ExamplePutRequest.swift in Sources */, @@ -491,6 +514,7 @@ 3F6806D129CBBE6B00B4F915 /* NetworkClient.swift in Sources */, 79D0C7562DE3973200D53241 /* TabBarView.swift in Sources */, 0C79EE632A76DD1900EE90EA /* RequestConstants.swift in Sources */, + 793BCC8E2DEEF05000DF1252 /* WebView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/FakeNFT/Docs/CartAnisimov.md b/FakeNFT/Docs/CartAnisimov.md new file mode 100644 index 0000000000..47edd948f2 --- /dev/null +++ b/FakeNFT/Docs/CartAnisimov.md @@ -0,0 +1,44 @@ +Анисимов Максим Сергеевич +Когорта: 22 +Группа: 2 +Эпик: Корзина + +Ссылка на доску: [ссылка](https://github.com/users/Maxoscaro/projects/1/views/4) + + +# Декомпозиция эпика Корзина + +## Модуль 1: + +#### Верстка +- Основной экран корзины и состояние пустой корзины (est: 1 час; fact: x часов). +- Ячейка с картинкой и ценой (est: 1 час; fact: x часов). +- Верстка кнопки покупки с ценой - (est: 1 час; fact: х часов) + +#### Логика +- Сортировка NFT (по названию, рейтингу, цене) (est: 1 час; fact: x часов). +- Сохранение выбранного способа сортировки (est: 1 час; fact: x часов). +- Заполнение ячеек данными из сервиса NFTService - (est: 2 часа; fact: х часов) +- Навигация на экран выбора способа оплаты (est: 40 минут; fact: x часов). + +## Модуль 2: + +#### Верстка +- Экран выбора способа оплаты (est: 1 час; fact: x часов). +- Ячейка с картинкой и криптовалютами (est: 50 минут; fact: x часов). +- Кнопка оплатить (est: 30 минут; fact: x часов). +- Ссылка на пользовательское соглашение (est: 30 минут; fact: x часов). + +#### Логика +- Открытие сайта пользователя в WebView (est: 30 минут; fact: x часов). +- Навигация на экран успешной покупки (est: 20 минут; fact: x часов). + +## Module 3: + +#### Верстка +- Экран удаления товара из корзины (est: 50 минут; fact: x часов). +- Экран успешной покупки (est: 1 час; fact: x часов). + +#### Логика +- Логика покупки: удаление из корзины и добавление в аккаунт пользователя (est: 2 часа; fact: x часов). +- Обработка возврата в каталог (est: 1 час; fact: x часов). diff --git a/FakeNFT/Docs/Statistics.md b/FakeNFT/Docs/Statistics.md new file mode 100644 index 0000000000..34b26815ed --- /dev/null +++ b/FakeNFT/Docs/Statistics.md @@ -0,0 +1,55 @@ +Кирпинева Анастасия Валерьевна + +Когорта: 22 + +Группа: 2 + +Эпик: Статистика + +Ссылка на доску: [ссылка](https://github.com/users/Maxoscaro/projects/1/views/3) + + +# Декомпозиция эпика Статистика + +## Модуль 1: + +#### Верстка +- Экран рейтинга (кнопка сортировки, список пользователей) (est: 30 минут; fact: x часов). +- Ячейка для списка пользователей на экране рейтинга (est: 1 час; fact: x часов). + +#### Логика +- Сортировка списка пользователей (по имени, рейтингу) (est: 1 час; fact: x часов). +- Сохранение выбранного способа сортировки (est: 1 час; fact: x часов). +- Навигация на экран информации о пользователе (est: 20 минут; fact: x часов). + +#### Работа с сетью +- Создать запрос на получение списка пользователей (est: 2 часа; fact: x часов). +- Подключить запрос (est: 1 час; fact: x часов). + +## Модуль 2: + +#### Верстка +- Экран информации о пользователе (аватар, имя, описание) (est: 30 минут; fact: x часов). +- Кнопка перехода на сайт пользователя (est: 30 минут; fact: x часов). +- Кнопка перехода на экран коллекции пользователя (est: 30 минут; fact: x часов). +- Создать WebView (est: 1 час; fact: x часов). + +#### Логика +- Открытие сайта пользователя в WebView (est: 30 минут; fact: x часов). +- Навигация на экран коллекции пользователя (est: 20 минут; fact: x часов). + +## Module 3: + +#### Верстка +- Экран коллекции пользователя (est: 20 минут; fact: x часов). +- Ячейка с информацией об NFT (est: 1 час; fact: x часов). + +#### Логика +- Обработка нажатия на сердечко (est: 1 час; fact: x часов). +- Обработка нажатия на кнопку корзины (добавить/удалить NFT) (est: 1 час; fact: x часов). + +#### Работа с сетью +- Загрузка и отображение изображений NFT (est: 1 час; fact: x часов). +- Запрос на добавление/удаление лайка (est: 1 час; fact: x часов). +- Запрос на добавление NFT в корзину (est: 1 час; fact: x часов). +- Запрос на удаление NFT из корзины (est: 1 час; fact: x часов). diff --git a/FakeNFT/FakeNftApp.swift b/FakeNFT/FakeNftApp.swift index 911bf63fe0..b47c6f0dff 100644 --- a/FakeNFT/FakeNftApp.swift +++ b/FakeNFT/FakeNftApp.swift @@ -9,9 +9,13 @@ import SwiftUI @main struct FakeNftApp: App { + + @StateObject var service = ServicesAssembly() + var body: some Scene { WindowGroup { ContentView() + .environmentObject(service) } } } diff --git a/FakeNFT/Scenes /Common/WebView/WebView.swift b/FakeNFT/Scenes /Common/WebView/WebView.swift new file mode 100644 index 0000000000..5271ab0e3c --- /dev/null +++ b/FakeNFT/Scenes /Common/WebView/WebView.swift @@ -0,0 +1,34 @@ +// +// WebView.swift +// FakeNFT +// +// Created by Anastasia on 03.06.2025. +// + +import SwiftUI + +struct WebView: View { + @State private var isLoading = true + let url: URL? + + var body: some View { + ZStack { + WebViewRepresentable(isLoading: $isLoading, url: url) + LoadingView() + .opacity(isLoading ? 1 : 0) + } + .edgesIgnoringSafeArea(.all) + .modifier(NavigationBarStyle( + title: nil, + backButtonHidden: false, + filterButtonHidden: true, + filterButtonTapHandler: { } + )) + } +} + +#Preview { + NavigationView { + WebView(url: URL(string: "https://practicum.yandex.ru")) + } +} diff --git a/FakeNFT/Scenes /Common/WebView/WebViewRepresentable.swift b/FakeNFT/Scenes /Common/WebView/WebViewRepresentable.swift new file mode 100644 index 0000000000..1e6afcde3e --- /dev/null +++ b/FakeNFT/Scenes /Common/WebView/WebViewRepresentable.swift @@ -0,0 +1,44 @@ +// +// WebViewRepresentable.swift +// FakeNFT +// +// Created by Anastasia on 03.06.2025. +// + +import SwiftUI +import WebKit + +struct WebViewRepresentable: UIViewRepresentable { + @Binding var isLoading: Bool + let url: URL? + + func makeUIView(context: Context) -> WKWebView { + WKWebView() + } + + func updateUIView(_ webView: WKWebView, context: Context) { + webView.navigationDelegate = context.coordinator + webView.isOpaque = false + webView.backgroundColor = .white + + if let url = url { + webView.load(URLRequest(url: url)) + } + } + + func makeCoordinator() -> Coordinator { + Coordinator(parent: self) + } + + final class Coordinator: NSObject, WKNavigationDelegate { + var parent: WebViewRepresentable + + init(parent: WebViewRepresentable) { + self.parent = parent + } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + parent.isLoading = false + } + } +} diff --git a/FakeNFT/Scenes /ContentView.swift b/FakeNFT/Scenes /ContentView.swift index 142928eacd..8c3235e23e 100644 --- a/FakeNFT/Scenes /ContentView.swift +++ b/FakeNFT/Scenes /ContentView.swift @@ -9,9 +9,9 @@ import SwiftUI struct ContentView: View { + @EnvironmentObject var service: ServicesAssembly @State private var isTabBarHidden = false @State private var selectedTab = Tab.profile - @StateObject private var cartViewModel = CartViewViewModel() var body: some View { ZStack(alignment: .bottom) { @@ -37,4 +37,5 @@ struct ContentView: View { #Preview { ContentView() + .environmentObject(ServicesAssembly()) } diff --git a/FakeNFT/Services/ServicesAssemly.swift b/FakeNFT/Services/ServicesAssemly.swift index 033f8bdf61..49b8dbd1c9 100644 --- a/FakeNFT/Services/ServicesAssemly.swift +++ b/FakeNFT/Services/ServicesAssemly.swift @@ -1,11 +1,13 @@ -final class ServicesAssembly { +import Foundation + +final class ServicesAssembly: ObservableObject { private let networkClient: NetworkClient private let nftStorage: NftStorage init( - networkClient: NetworkClient, - nftStorage: NftStorage + networkClient: NetworkClient = DefaultNetworkClient(), + nftStorage: NftStorage = NftStorageImpl() ) { self.networkClient = networkClient self.nftStorage = nftStorage