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
160 changes: 160 additions & 0 deletions FakeNFT.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

42 changes: 21 additions & 21 deletions FakeNFT/Docs/Statistics.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,42 @@
## Модуль 1:

#### Верстка
- Экран рейтинга (кнопка сортировки, список пользователей) (est: 30 минут; fact: x часов).
- Ячейка для списка пользователей на экране рейтинга (est: 1 час; fact: x часов).
- Экран рейтинга (кнопка сортировки, список пользователей) (est: 30 минут; fact: 30 минут).
- Ячейка для списка пользователей на экране рейтинга (est: 1 час; fact: 30 минут).

#### Логика
- Сортировка списка пользователей (по имени, рейтингу) (est: 1 час; fact: x часов).
- Сохранение выбранного способа сортировки (est: 1 час; fact: x часов).
- Навигация на экран информации о пользователе (est: 20 минут; fact: x часов).
- Сортировка списка пользователей (по имени, рейтингу) (est: 1 час; fact: 20 минут).
- Сохранение выбранного способа сортировки (est: 1 час; fact: 25 минут).
- Навигация на экран информации о пользователе (est: 20 минут; fact: 15 минут).

#### Работа с сетью
- Создать запрос на получение списка пользователей (est: 2 часа; fact: x часов).
- Подключить запрос (est: 1 час; fact: x часов).
- Создать запрос на получение списка пользователей (est: 2 часа; fact: 1 час).
- Подключить запрос (est: 1 час; fact: 30 минут).

## Модуль 2:

#### Верстка
- Экран информации о пользователе (аватар, имя, описание) (est: 30 минут; fact: x часов).
- Кнопка перехода на сайт пользователя (est: 30 минут; fact: x часов).
- Кнопка перехода на экран коллекции пользователя (est: 30 минут; fact: x часов).
- Создать WebView (est: 1 час; fact: x часов).
- Экран информации о пользователе (аватар, имя, описание) (est: 30 минут; fact: 30 минут).
- Кнопка перехода на сайт пользователя (est: 30 минут; fact: 20 минут).
- Кнопка перехода на экран коллекции пользователя (est: 30 минут; fact: 30 минут).
- Создать WebView (est: 1 час; fact: 1 час).

#### Логика
- Открытие сайта пользователя в WebView (est: 30 минут; fact: x часов).
- Навигация на экран коллекции пользователя (est: 20 минут; fact: x часов).
- Открытие сайта пользователя в WebView (est: 30 минут; fact: 10 минут).
- Навигация на экран коллекции пользователя (est: 20 минут; fact: 15 минут).

## Module 3:

#### Верстка
- Экран коллекции пользователя (est: 20 минут; fact: x часов).
- Ячейка с информацией об NFT (est: 1 час; fact: x часов).
- Экран коллекции пользователя (est: 20 минут; fact: 20 минут).
- Ячейка с информацией об NFT (est: 1 час; fact: 50 минут).

#### Логика
- Обработка нажатия на сердечко (est: 1 час; fact: x часов).
- Обработка нажатия на кнопку корзины (добавить/удалить NFT) (est: 1 час; fact: x часов).
- Обработка нажатия на сердечко (est: 1 час; fact: 1 час).
- Обработка нажатия на кнопку корзины (добавить/удалить NFT) (est: 1 час; fact: 1 час).

#### Работа с сетью
- Загрузка и отображение изображений NFT (est: 1 час; fact: x часов).
- Запрос на добавление/удаление лайка (est: 1 час; fact: x часов).
- Запрос на добавление NFT в корзину (est: 1 час; fact: x часов).
- Запрос на удаление NFT из корзины (est: 1 час; fact: x часов).
- Загрузка и отображение изображений NFT (est: 1 час; fact: 30 минут).
- Запрос на добавление/удаление лайка (est: 1 час; fact: 2 часа).
- Запрос на добавление NFT в корзину (est: 1 час; fact: 30 минут).
- Запрос на удаление NFT из корзины (est: 1 час; fact: 30 минут).
16 changes: 16 additions & 0 deletions FakeNFT/Models/Network/NftInfo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// NftInfo.swift
// FakeNFT
//
// Created by Anastasia on 07.06.2025.
//

import Foundation

struct NftInfo: Decodable, Hashable {
let id: String
let name: String
let images: [String]
let rating: Int
let price: Double
}
18 changes: 18 additions & 0 deletions FakeNFT/Models/Network/User.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// User.swift
// FakeNFT
//
// Created by Anastasia on 03.06.2025.
//

import Foundation

struct User: Decodable, Identifiable {
let id: String
let name: String
let avatar: String
let description: String?
let website: String
let nfts: [String]
let rating: String
}
12 changes: 12 additions & 0 deletions FakeNFT/Models/Network/UserLikes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// UserLikes.swift
// FakeNFT
//
// Created by Anastasia on 07.06.2025.
//

import Foundation

struct UserLikes: Decodable {
let likes: [String]
}
12 changes: 12 additions & 0 deletions FakeNFT/Models/Network/UserOrders.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// UserOrders.swift
// FakeNFT
//
// Created by Anastasia on 07.06.2025.
//

import Foundation

struct UserOrders: Decodable {
let nfts: [String]
}
59 changes: 59 additions & 0 deletions FakeNFT/RatingViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// RatingViewModel.swift
// FakeNFT
//
// Created by Anastasia on 03.06.2025.
//

import SwiftUI

@MainActor
final class RatingViewModel: ObservableObject {
@Published var filteredUsers: [User] = []
@Published var isLoading: Bool = false
@Published var isShowingFilterSheet: Bool = false
@Published var isShowingErrorAlert: Bool = false
private var users: [User] = []
private let usersService: UsersService

@AppStorage("ratingSort") private var currentSorting: RatingFilterType = .rating

init(usersService: UsersService) {
self.usersService = usersService
}

func loadUsers() {
if !users.isEmpty { return }
isLoading = true

usersService.loadUsers { [weak self] result in
DispatchQueue.main.async {
guard let self else { return }
self.isLoading = false
switch result {
case .success(let users):
self.users = users
self.filterUsers(by: self.currentSorting)
case .failure(_):
self.isShowingErrorAlert = true
}
}
}
}

func filterUsers(by type: RatingFilterType) {
switch type {
case .name:
currentSorting = .name
filteredUsers = users.sorted { $0.name.lowercased() < $1.name.lowercased() }
case .rating:
currentSorting = .rating
filteredUsers = users.sorted { Int($0.rating) ?? 0 < Int($1.rating) ?? 0 }
}
}
}

enum RatingFilterType: String {
case name
case rating
}
5 changes: 4 additions & 1 deletion FakeNFT/Scenes /ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ struct ContentView: View {
Text("Cart")
.tag(Tab.catalog)

Text("Statistics")
RatingView(
viewModel: RatingViewModel(usersService: service.usersService),
isTabBarHidden: $isTabBarHidden
)
.tag(Tab.statistics)
}
if !isTabBarHidden {
Expand Down
34 changes: 34 additions & 0 deletions FakeNFT/Scenes /Statistics/Common/List/RatingList.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// RatingList.swift
// FakeNFT
//
// Created by Anastasia on 03.06.2025.
//

import SwiftUI

struct RatingList<Content: View>: View {

// MARK: - Properties

let isLoading: Bool
@ViewBuilder var content: Content

// MARK: - Content

var body: some View {
if isLoading {
LoadingView()
} else {
ScrollView(showsIndicators: false) {
LazyVStack(spacing: StatisticsConstants.anchorSmall) {
content
}
}
}
}
}

#Preview {
RatingList(isLoading: true) {}
}
57 changes: 57 additions & 0 deletions FakeNFT/Scenes /Statistics/Common/List/UserNFTCollection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// NFTCollection.swift
// FakeNFT
//
// Created by Anastasia on 07.06.2025.
//

import SwiftUI

struct UserNFTCollection: View {

let nftInfo: [NftInfo]
let userLikes: UserLikes
let userOrders: UserOrders
var likeTapHandler: (NftInfo) -> Void
var cartTapHandler: (NftInfo) -> Void

let columns = [
GridItem(.flexible(), alignment: .top),
GridItem(.flexible(), alignment: .top),
GridItem(.flexible(), alignment: .top)
]

var body: some View {
ScrollView {
LazyVGrid(columns: columns) {
ForEach(nftInfo, id: \.self) { nft in
CollectionRow(
nft: nft,
userLikes: userLikes,
userOrders: userOrders,
likeTapHandler: likeTapHandler,
cartTapHandler: cartTapHandler
)
}
}
.padding(.horizontal)
}
}
}

#Preview {
UserNFTCollection(
nftInfo: [
NftInfo(
id: "",
name: "",
images: [""],
rating: 1,
price: 3.98
)],
userLikes: UserLikes(likes: []),
userOrders: UserOrders(nfts: []),
likeTapHandler: {_ in },
cartTapHandler: {_ in }
)
}
Loading