main menu
This commit is contained in:
		
							parent
							
								
									842033dcb5
								
							
						
					
					
						commit
						1c01c2a340
					
				@ -395,7 +395,7 @@
 | 
			
		||||
				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 | 
			
		||||
				CODE_SIGN_ENTITLEMENTS = yobble/yobble.entitlements;
 | 
			
		||||
				CODE_SIGN_STYLE = Automatic;
 | 
			
		||||
				CURRENT_PROJECT_VERSION = 1;
 | 
			
		||||
				CURRENT_PROJECT_VERSION = 2;
 | 
			
		||||
				DEVELOPMENT_TEAM = V22H44W47J;
 | 
			
		||||
				ENABLE_HARDENED_RUNTIME = YES;
 | 
			
		||||
				ENABLE_PREVIEWS = YES;
 | 
			
		||||
@ -435,7 +435,7 @@
 | 
			
		||||
				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 | 
			
		||||
				CODE_SIGN_ENTITLEMENTS = yobble/yobble.entitlements;
 | 
			
		||||
				CODE_SIGN_STYLE = Automatic;
 | 
			
		||||
				CURRENT_PROJECT_VERSION = 1;
 | 
			
		||||
				CURRENT_PROJECT_VERSION = 2;
 | 
			
		||||
				DEVELOPMENT_TEAM = V22H44W47J;
 | 
			
		||||
				ENABLE_HARDENED_RUNTIME = YES;
 | 
			
		||||
				ENABLE_PREVIEWS = YES;
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,104 @@
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct TopBarView: View {
 | 
			
		||||
    var title: String
 | 
			
		||||
    
 | 
			
		||||
    // Состояния для ProfileTab
 | 
			
		||||
    @Binding var selectedAccount: String
 | 
			
		||||
//    @Binding var sheetType: ProfileTab.SheetType?
 | 
			
		||||
    var accounts: [String]
 | 
			
		||||
    var viewModel: LoginViewModel
 | 
			
		||||
    
 | 
			
		||||
    // Привязка для управления боковым меню
 | 
			
		||||
    @Binding var isSideMenuPresented: Bool
 | 
			
		||||
    
 | 
			
		||||
    var isHomeTab: Bool {
 | 
			
		||||
        return title == "Home"
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    var isProfileTab: Bool {
 | 
			
		||||
        return title == "Profile"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack(spacing: 0) {
 | 
			
		||||
            HStack {
 | 
			
		||||
                // Кнопка "Гамбургер" для открытия меню
 | 
			
		||||
                Button(action: {
 | 
			
		||||
                    withAnimation {
 | 
			
		||||
                        isSideMenuPresented.toggle()
 | 
			
		||||
                    }
 | 
			
		||||
                }) {
 | 
			
		||||
                    Image(systemName: "line.horizontal.3")
 | 
			
		||||
                        .imageScale(.large)
 | 
			
		||||
                        .foregroundColor(.primary)
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
//                Spacer()
 | 
			
		||||
                
 | 
			
		||||
                if isHomeTab{
 | 
			
		||||
                    Text("Yobble")
 | 
			
		||||
                        .font(.largeTitle)
 | 
			
		||||
                        .fontWeight(.bold)
 | 
			
		||||
                    Spacer()
 | 
			
		||||
                } else if isProfileTab {
 | 
			
		||||
                    Spacer()
 | 
			
		||||
                    Button(action: { }) {
 | 
			
		||||
                        HStack(spacing: 4) {
 | 
			
		||||
                            Text(selectedAccount)
 | 
			
		||||
                                .font(.headline)
 | 
			
		||||
                                .foregroundColor(.primary)
 | 
			
		||||
                            Image(systemName: "chevron.down")
 | 
			
		||||
                                .font(.subheadline)
 | 
			
		||||
                                .foregroundColor(.gray)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    Spacer()
 | 
			
		||||
                } else {
 | 
			
		||||
                    Text(title)
 | 
			
		||||
                        .font(.largeTitle)
 | 
			
		||||
                        .fontWeight(.bold)
 | 
			
		||||
                    Spacer()
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                if isHomeTab{
 | 
			
		||||
                    HStack(spacing: 20) {
 | 
			
		||||
                        // Кнопка поиска
 | 
			
		||||
                        Button(action: {
 | 
			
		||||
                            // пока ничего не делаем
 | 
			
		||||
                        }) {
 | 
			
		||||
                            Image(systemName: "magnifyingglass")
 | 
			
		||||
                                .imageScale(.large)
 | 
			
		||||
                                .foregroundColor(.primary)
 | 
			
		||||
                        }
 | 
			
		||||
                        // Кнопка уведомлений
 | 
			
		||||
                        Button(action: {
 | 
			
		||||
                            // пока ничего не делаем
 | 
			
		||||
                        }) {
 | 
			
		||||
                            Image(systemName: "bell")
 | 
			
		||||
                                .imageScale(.large)
 | 
			
		||||
                                .foregroundColor(.primary)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else if isProfileTab {
 | 
			
		||||
                    NavigationLink(destination: SettingsView(viewModel: viewModel)) {
 | 
			
		||||
                        Image(systemName: "wrench")
 | 
			
		||||
                            .imageScale(.large)
 | 
			
		||||
                            .foregroundColor(.primary)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .padding()
 | 
			
		||||
            .frame(height: 50) // Стандартная высота для нав. бара
 | 
			
		||||
            
 | 
			
		||||
            Divider()
 | 
			
		||||
        }
 | 
			
		||||
        .background(Color(UIColor.systemBackground))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct TopBarView_Previews: PreviewProvider {
 | 
			
		||||
    static var previews: some View {
 | 
			
		||||
        /*@START_MENU_TOKEN@*/Text("Hello, World!")/*@END_MENU_TOKEN@*/
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -99,11 +99,12 @@ class AuthService {
 | 
			
		||||
 | 
			
		||||
                do {
 | 
			
		||||
                    let decoder = JSONDecoder()
 | 
			
		||||
                    let loginResponse = try decoder.decode(LoginResponse.self, from: data)
 | 
			
		||||
                    let loginResponse = try decoder.decode(APIResponse<LoginPayload>.self, from: data).data
 | 
			
		||||
                    
 | 
			
		||||
                    // Сохраняем токены в Keychain
 | 
			
		||||
                    KeychainService.shared.save(loginResponse.access_token, forKey: "access_token", service: username)
 | 
			
		||||
                    KeychainService.shared.save(loginResponse.refresh_token, forKey: "refresh_token", service: username)
 | 
			
		||||
                    KeychainService.shared.save(loginResponse.user_id, forKey: "user_id", service: username)
 | 
			
		||||
                    UserDefaults.standard.set(username, forKey: "currentUser")
 | 
			
		||||
                    
 | 
			
		||||
                    completion(true, nil)
 | 
			
		||||
@ -160,7 +161,7 @@ class AuthService {
 | 
			
		||||
                
 | 
			
		||||
                if (200...299).contains(httpResponse.statusCode) {
 | 
			
		||||
                    do {
 | 
			
		||||
                        let _ = try decoder.decode(RegisterResponse.self, from: data)
 | 
			
		||||
                        let _ = try decoder.decode(APIResponse<RegisterPayload>.self, from: data)
 | 
			
		||||
                        if AppConfig.DEBUG{ print("Регистрация успешна. Пытаемся сразу войти...")}
 | 
			
		||||
 | 
			
		||||
                        // Сразу логинимся
 | 
			
		||||
@ -177,8 +178,8 @@ class AuthService {
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Ошибка сервера — пробуем распарсить message
 | 
			
		||||
                    if let errorResponseMessage = try? decoder.decode(ErrorResponseMessage.self, from: data),
 | 
			
		||||
                       let message = errorResponseMessage.message {
 | 
			
		||||
                    if let errorResponseMessage = try? decoder.decode(ErrorResponse.self, from: data),
 | 
			
		||||
                       let message = errorResponseMessage.data?.message {
 | 
			
		||||
                        
 | 
			
		||||
                        if let jsonString = String(data: data, encoding: .utf8) {
 | 
			
		||||
                            if AppConfig.DEBUG{ print("Raw JSON:", jsonString)}
 | 
			
		||||
@ -270,25 +271,31 @@ class AuthService {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct LoginResponse: Decodable {
 | 
			
		||||
struct APIResponse<T: Decodable>: Decodable {
 | 
			
		||||
    let status: String
 | 
			
		||||
    let data: T
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct LoginPayload: Decodable {
 | 
			
		||||
    let access_token: String
 | 
			
		||||
    let refresh_token: String
 | 
			
		||||
    let token_type: String
 | 
			
		||||
    let user_id: String
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct TokenRefreshResponse: Decodable {
 | 
			
		||||
    let status: String
 | 
			
		||||
struct TokenRefreshPayload: Decodable {
 | 
			
		||||
    let access_token: String
 | 
			
		||||
    let token_type: String
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct RegisterResponse: Decodable {
 | 
			
		||||
    let status: String
 | 
			
		||||
struct RegisterPayload: Decodable {
 | 
			
		||||
    let message: String
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ErrorResponseMessage: Decodable {
 | 
			
		||||
    let status: String?
 | 
			
		||||
struct ErrorPayload: Decodable {
 | 
			
		||||
    let message: String?
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ErrorResponse: Decodable {
 | 
			
		||||
    let status: String?
 | 
			
		||||
    let data: ErrorPayload?
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,58 +1,13 @@
 | 
			
		||||
{
 | 
			
		||||
  "sourceLanguage" : "en",
 | 
			
		||||
  "strings" : {
 | 
			
		||||
    "@yourusername" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "🌍" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_error_empty_response" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_error_invalid_invitation_code" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_error_invalid_request" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_error_invalid_response" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_error_invitation_expired" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_error_invitation_not_active" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_error_invitation_usage_limit" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_error_login_already_registered" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_error_network" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_error_parsing_response" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_error_registration_disabled" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_error_registration_forbidden" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_error_serialization" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_error_server_error" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_error_server_unavailable" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_error_too_many_requests" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "AuthService_login_success_but_failed" : {
 | 
			
		||||
    "CATEGORY" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Hello, world!" : {
 | 
			
		||||
@ -94,15 +49,75 @@
 | 
			
		||||
    },
 | 
			
		||||
    "profile_down_text_3" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Push-уведомления" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "SERVICES" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Yobble" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Your Name" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Активные сессии" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Безопасность" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Войти" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Выйти из аккаунта" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Данные" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Двухфакторная аутентификация" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Другое" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Заглушка: Push-уведомления" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Заглушка: Активные сессии" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Заглушка: Двухфакторная аутентификация" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Заглушка: Другие настройки" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Заглушка: Обратная связь" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Заглушка: Сменить пароль" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Заглушка: Хранилище данных" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Заглушка: Частые вопросы" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Закрыть" : {
 | 
			
		||||
      "comment" : "Закрыть"
 | 
			
		||||
    },
 | 
			
		||||
    "Зарегистрироваться" : {
 | 
			
		||||
      "comment" : "Зарегистрироваться"
 | 
			
		||||
    },
 | 
			
		||||
    "Здесь будут чаты" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Здесь не будут чаты" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Инвайт-код (необязательно)" : {
 | 
			
		||||
      "comment" : "Инвайт-код"
 | 
			
		||||
@ -112,9 +127,27 @@
 | 
			
		||||
    },
 | 
			
		||||
    "Логин должен быть от 3 до 32 символов (английские буквы, цифры, _)" : {
 | 
			
		||||
      "comment" : "Логин должен быть от 3 до 32 символов (английские буквы, цифры, _)"
 | 
			
		||||
    },
 | 
			
		||||
    "Логин уже занят." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Мой профиль" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Настройки" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Не удалось обработать ответ сервера." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Не удалось сериализовать данные запроса." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Неверный запрос (400)." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Неверный код приглашения." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Неверный логин" : {
 | 
			
		||||
      "comment" : "Неверный логин"
 | 
			
		||||
@ -136,6 +169,18 @@
 | 
			
		||||
    },
 | 
			
		||||
    "Нет аккаунта? Регистрация" : {
 | 
			
		||||
      "comment" : "Регистрация"
 | 
			
		||||
    },
 | 
			
		||||
    "О приложении" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Обратная связь" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Описание" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Отображаемое имя" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Ошибка авторизации" : {
 | 
			
		||||
 | 
			
		||||
@ -160,21 +205,75 @@
 | 
			
		||||
    },
 | 
			
		||||
    "Пароль должен быть от 8 до 128 символов" : {
 | 
			
		||||
      "comment" : "Пароль должен быть от 6 до 32 символов"
 | 
			
		||||
    },
 | 
			
		||||
    "Поддержка" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Подтверждение пароля" : {
 | 
			
		||||
      "comment" : "Подтверждение пароля"
 | 
			
		||||
    },
 | 
			
		||||
    "Приглашение достигло лимита использования." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Приглашение истекло." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Приглашение не активно." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Приложение" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Применить" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Произошла ошибка." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Профиль" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Публичная информация" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Пустой ответ от сервера." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Регистрация" : {
 | 
			
		||||
      "comment" : "Регистрация"
 | 
			
		||||
    },
 | 
			
		||||
    "Регистрация временно недоступна." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Регистрация выполнена, но вход не удался." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Регистрация запрещена." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Редактировать профиль" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Сервер не отвечает. Попробуйте позже." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Слишком много запросов." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Сменить пароль" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Тёмная тема" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Уведомления" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Частые вопросы" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Язык" : {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "version" : "1.0"
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
//
 | 
			
		||||
//  ChatsTab.swift
 | 
			
		||||
//  VolnahubApp
 | 
			
		||||
//
 | 
			
		||||
//  Created by cheykrym on 09/06/2025.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct ChatsTab: View {
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack {
 | 
			
		||||
            Text("Здесь будут чаты")
 | 
			
		||||
                .font(.title)
 | 
			
		||||
                .foregroundColor(.gray)
 | 
			
		||||
            
 | 
			
		||||
            Spacer()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,84 @@
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct CustomTabBar: View {
 | 
			
		||||
    @Binding var selectedTab: Int
 | 
			
		||||
    var onCreate: () -> Void
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        HStack {
 | 
			
		||||
            // Tab 1: Feed
 | 
			
		||||
            TabBarButton(systemName: "list.bullet.rectangle", text: "Лента", isSelected: selectedTab == 0) {
 | 
			
		||||
                selectedTab = 0
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Tab 2: Search
 | 
			
		||||
            TabBarButton(systemName: "magnifyingglass", text: "Поиск", isSelected: selectedTab == 1) {
 | 
			
		||||
                selectedTab = 1
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Create Button
 | 
			
		||||
            CreateButton {
 | 
			
		||||
                onCreate()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Tab 3: Chats
 | 
			
		||||
            TabBarButton(systemName: "bubble.left.and.bubble.right.fill", text: "Чаты", isSelected: selectedTab == 2) {
 | 
			
		||||
                selectedTab = 2
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Tab 4: Profile
 | 
			
		||||
            TabBarButton(systemName: "person.crop.square", text: "Лицо", isSelected: selectedTab == 3) {
 | 
			
		||||
                selectedTab = 3
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .padding(.horizontal)
 | 
			
		||||
        .padding(.top, 1)
 | 
			
		||||
        .padding(.bottom, 30) // Добавляем отступ снизу
 | 
			
		||||
//        .background(Color(.systemGray6))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct TabBarButton: View {
 | 
			
		||||
    let systemName: String
 | 
			
		||||
    let text: String
 | 
			
		||||
    let isSelected: Bool
 | 
			
		||||
    let action: () -> Void
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        Button(action: action) {
 | 
			
		||||
            VStack(spacing: 4) {
 | 
			
		||||
                Image(systemName: systemName)
 | 
			
		||||
                    .font(.system(size: 22))
 | 
			
		||||
                Text(text)
 | 
			
		||||
//                    .font(.caption)
 | 
			
		||||
                    .font(.system(size: 12))
 | 
			
		||||
            }
 | 
			
		||||
            .foregroundColor(isSelected ? .accentColor : .gray)
 | 
			
		||||
        }
 | 
			
		||||
        .frame(maxWidth: .infinity)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct CreateButton: View {
 | 
			
		||||
    let action: () -> Void
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        Button(action: action) {
 | 
			
		||||
            Image(systemName: "plus")
 | 
			
		||||
                .font(.system(size: 24, weight: .bold))
 | 
			
		||||
                .foregroundColor(.white)
 | 
			
		||||
                .padding(16)
 | 
			
		||||
//                .background(Color.accentColor)
 | 
			
		||||
                .background(
 | 
			
		||||
                    LinearGradient(
 | 
			
		||||
                        gradient: Gradient(colors: [.blue, .white]),
 | 
			
		||||
                        startPoint: .top,
 | 
			
		||||
                        endPoint: .bottom
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
                .clipShape(Circle())
 | 
			
		||||
                .shadow(radius: 4)
 | 
			
		||||
        }
 | 
			
		||||
        .offset(y: -3)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,136 @@
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct MainView: View {
 | 
			
		||||
    @ObservedObject var viewModel: LoginViewModel
 | 
			
		||||
    @State private var selectedTab: Int = 0
 | 
			
		||||
//    @StateObject private var newHomeTabViewModel = NewHomeTabViewModel()
 | 
			
		||||
    
 | 
			
		||||
    // Состояния для TopBarView
 | 
			
		||||
    @State private var selectedAccount = "@user1"
 | 
			
		||||
    @State private var accounts = ["@user1", "@user2", "@user3"]
 | 
			
		||||
//    @State private var sheetType: ProfileTab.SheetType? = nil
 | 
			
		||||
    
 | 
			
		||||
    // Состояния для бокового меню
 | 
			
		||||
    @State private var isSideMenuPresented = false
 | 
			
		||||
    @State private var menuOffset: CGFloat = 0
 | 
			
		||||
    
 | 
			
		||||
    private var tabTitle: String {
 | 
			
		||||
        switch selectedTab {
 | 
			
		||||
        case 0: return "Home"
 | 
			
		||||
        case 1: return "Search"
 | 
			
		||||
        case 2: return "Chats"
 | 
			
		||||
        case 3: return "Profile"
 | 
			
		||||
        default: return "Home"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private var menuWidth: CGFloat {
 | 
			
		||||
        UIScreen.main.bounds.width * 0.8
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        NavigationView {
 | 
			
		||||
            ZStack(alignment: .leading) { // Выравниваем ZStack по левому краю
 | 
			
		||||
                // Основной контент
 | 
			
		||||
                VStack(spacing: 0) {
 | 
			
		||||
                    TopBarView(
 | 
			
		||||
                        title: tabTitle,
 | 
			
		||||
                        selectedAccount: $selectedAccount,
 | 
			
		||||
                        accounts: accounts,
 | 
			
		||||
                        viewModel: viewModel,
 | 
			
		||||
                        isSideMenuPresented: $isSideMenuPresented
 | 
			
		||||
                    )
 | 
			
		||||
                    
 | 
			
		||||
                    ZStack {
 | 
			
		||||
                        NewHomeTab()
 | 
			
		||||
                            .opacity(selectedTab == 0 ? 1 : 0)
 | 
			
		||||
                        
 | 
			
		||||
                        SearchTab()
 | 
			
		||||
                            .opacity(selectedTab == 1 ? 1 : 0)
 | 
			
		||||
                        
 | 
			
		||||
                        ChatsTab()
 | 
			
		||||
                            .opacity(selectedTab == 2 ? 1 : 0)
 | 
			
		||||
                        
 | 
			
		||||
                        ProfileTab()
 | 
			
		||||
                            .opacity(selectedTab == 3 ? 1 : 0)
 | 
			
		||||
                    }
 | 
			
		||||
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
 | 
			
		||||
                    
 | 
			
		||||
                    CustomTabBar(selectedTab: $selectedTab) {
 | 
			
		||||
                        print("Create button tapped")
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                .frame(maxWidth: .infinity, maxHeight: .infinity) // Убедимся, что основной контент занимает все пространство
 | 
			
		||||
                .ignoresSafeArea(edges: .bottom)
 | 
			
		||||
                .navigationBarHidden(true)
 | 
			
		||||
//                .sheet(item: $sheetType) { type in
 | 
			
		||||
//                    // ... sheet presentation logic
 | 
			
		||||
//                }
 | 
			
		||||
                
 | 
			
		||||
                // Затемнение и закрытие по тапу
 | 
			
		||||
                Color.black
 | 
			
		||||
                    .opacity(Double(menuOffset / menuWidth) * 0.4)
 | 
			
		||||
                    .ignoresSafeArea()
 | 
			
		||||
                    .onTapGesture {
 | 
			
		||||
                        withAnimation(.easeInOut) {
 | 
			
		||||
                            isSideMenuPresented = false
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    .allowsHitTesting(menuOffset > 0)
 | 
			
		||||
 | 
			
		||||
                // Боковое меню
 | 
			
		||||
                SideMenuView(isPresented: $isSideMenuPresented)
 | 
			
		||||
                    .frame(width: menuWidth)
 | 
			
		||||
                    .offset(x: -menuWidth + menuOffset) // Новая логика смещения
 | 
			
		||||
                    .ignoresSafeArea(edges: .vertical)
 | 
			
		||||
            }
 | 
			
		||||
            .gesture(
 | 
			
		||||
                DragGesture()
 | 
			
		||||
                    .onChanged { gesture in
 | 
			
		||||
                        if !isSideMenuPresented && gesture.startLocation.x > 60 { return }
 | 
			
		||||
 | 
			
		||||
                        let translation = gesture.translation.width
 | 
			
		||||
                        
 | 
			
		||||
                        // Определяем базовое смещение в зависимости от того, открыто меню или нет
 | 
			
		||||
                        let baseOffset = isSideMenuPresented ? menuWidth : 0
 | 
			
		||||
                        
 | 
			
		||||
                        // Новое смещение — это база плюс текущий свайп
 | 
			
		||||
                        let newOffset = baseOffset + translation
 | 
			
		||||
                        
 | 
			
		||||
                        // Жестко ограничиваем итоговое смещение между 0 и шириной меню
 | 
			
		||||
                        self.menuOffset = max(0, min(menuWidth, newOffset))
 | 
			
		||||
                    }
 | 
			
		||||
                    .onEnded { gesture in
 | 
			
		||||
                        if !isSideMenuPresented && gesture.startLocation.x > 60 { return }
 | 
			
		||||
                        
 | 
			
		||||
                        let threshold = menuWidth * 0.4
 | 
			
		||||
                        
 | 
			
		||||
                        withAnimation(.easeInOut) {
 | 
			
		||||
                            if self.menuOffset > threshold {
 | 
			
		||||
                                isSideMenuPresented = true
 | 
			
		||||
                            } else {
 | 
			
		||||
                                isSideMenuPresented = false
 | 
			
		||||
                            }
 | 
			
		||||
                            // Устанавливаем финальное смещение после анимации
 | 
			
		||||
                            self.menuOffset = isSideMenuPresented ? menuWidth : 0
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        .navigationViewStyle(StackNavigationViewStyle())
 | 
			
		||||
        .onChange(of: isSideMenuPresented) { presented in
 | 
			
		||||
            // Плавная анимация при нажатии на кнопку, а не только при жесте
 | 
			
		||||
            withAnimation(.easeInOut) {
 | 
			
		||||
                menuOffset = presented ? menuWidth : 0
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct MainView_Previews: PreviewProvider {
 | 
			
		||||
    static var previews: some View {
 | 
			
		||||
        let mockViewModel = LoginViewModel()
 | 
			
		||||
        MainView(viewModel: mockViewModel)
 | 
			
		||||
            .environmentObject(ThemeManager())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,17 @@
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct NewHomeTab: View {
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack {
 | 
			
		||||
            VStack {
 | 
			
		||||
                Text("Здесь не будут чаты")
 | 
			
		||||
                    .font(.title)
 | 
			
		||||
                    .foregroundColor(.gray)
 | 
			
		||||
                
 | 
			
		||||
                Spacer()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
//        .background(Color(.secondarySystemBackground)) // Фон для всей вкладки
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct ProfileTab: View {
 | 
			
		||||
//    @Binding var sheetType: SheetType?
 | 
			
		||||
 | 
			
		||||
//    enum SheetType: Identifiable {
 | 
			
		||||
//        case accountShare
 | 
			
		||||
//        var id: Int { self.hashValue }
 | 
			
		||||
//    }
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack {
 | 
			
		||||
            Text("Здесь не будут чаты")
 | 
			
		||||
                .font(.title)
 | 
			
		||||
                .foregroundColor(.gray)
 | 
			
		||||
            
 | 
			
		||||
            Spacer()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,14 @@
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct SearchTab: View {
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack {
 | 
			
		||||
            Text("Здесь не будут чаты")
 | 
			
		||||
                .font(.title)
 | 
			
		||||
                .foregroundColor(.gray)
 | 
			
		||||
            
 | 
			
		||||
            Spacer()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,24 @@
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct EditProfileView: View {
 | 
			
		||||
    @State private var displayName = ""
 | 
			
		||||
    @State private var description = ""
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        Form {
 | 
			
		||||
            Section(header: Text("Публичная информация")) {
 | 
			
		||||
                TextField("Отображаемое имя", text: $displayName)
 | 
			
		||||
                TextField("Описание", text: $description)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Button(action: {
 | 
			
		||||
                // Действие для сохранения профиля
 | 
			
		||||
                print("DisplayName: \(displayName)")
 | 
			
		||||
                print("Description: \(description)")
 | 
			
		||||
            }) {
 | 
			
		||||
                Text("Применить")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .navigationTitle("Редактировать профиль")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,98 @@
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct SettingsView: View {
 | 
			
		||||
    @ObservedObject var viewModel: LoginViewModel
 | 
			
		||||
    @AppStorage("isDarkMode") private var isDarkMode: Bool = true
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        Form {
 | 
			
		||||
            // MARK: - Профиль
 | 
			
		||||
            Section(header: Text("Профиль")) {
 | 
			
		||||
                NavigationLink(destination: EditProfileView()) {
 | 
			
		||||
                    Label("Мой профиль", systemImage: "person.crop.circle")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // MARK: - Безопасность
 | 
			
		||||
            Section(header: Text("Безопасность")) {
 | 
			
		||||
                NavigationLink(destination: Text("Заглушка: Сменить пароль")) {
 | 
			
		||||
                    Label("Сменить пароль", systemImage: "key")
 | 
			
		||||
                }
 | 
			
		||||
                NavigationLink(destination: Text("Заглушка: Двухфакторная аутентификация")) {
 | 
			
		||||
                    Label("Двухфакторная аутентификация", systemImage: "lock.shield")
 | 
			
		||||
                }
 | 
			
		||||
                NavigationLink(destination: Text("Заглушка: Активные сессии")) {
 | 
			
		||||
                    Label("Активные сессии", systemImage: "iphone")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // MARK: - Приложение
 | 
			
		||||
            Section(header: Text("Приложение")) {
 | 
			
		||||
                Button(action: openLanguageSettings) {
 | 
			
		||||
                    Label("Язык", systemImage: "globe")
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Toggle(isOn: $isDarkMode) {
 | 
			
		||||
                    Label("Тёмная тема", systemImage: "moon.fill")
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                NavigationLink(destination: Text("Заглушка: Хранилище данных")) {
 | 
			
		||||
                    Label("Данные", systemImage: "externaldrive")
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                NavigationLink(destination: Text("Заглушка: Другие настройки")) {
 | 
			
		||||
                    Label("Другое", systemImage: "ellipsis.circle")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // MARK: - Уведомления
 | 
			
		||||
            Section(header: Text("Уведомления")) {
 | 
			
		||||
                NavigationLink(destination: Text("Заглушка: Push-уведомления")) {
 | 
			
		||||
                    Label("Push-уведомления", systemImage: "bell")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // MARK: - Поддержка
 | 
			
		||||
            Section(header: Text("Поддержка")) {
 | 
			
		||||
                NavigationLink(destination: Text("Заглушка: Частые вопросы")) {
 | 
			
		||||
                    Label("Частые вопросы", systemImage: "questionmark.circle")
 | 
			
		||||
                }
 | 
			
		||||
                NavigationLink(destination: Text("Заглушка: Обратная связь")) {
 | 
			
		||||
                    Label("Обратная связь", systemImage: "paperplane")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // MARK: - О приложении
 | 
			
		||||
            Section(header: Text("О приложении")) {
 | 
			
		||||
                VStack(alignment: .leading, spacing: 6) {
 | 
			
		||||
                    Text(AppInfo.text_1)
 | 
			
		||||
                    Text(AppInfo.text_2)
 | 
			
		||||
                    Text(AppInfo.text_3)
 | 
			
		||||
                }
 | 
			
		||||
                .font(.footnote)
 | 
			
		||||
                .foregroundColor(.gray)
 | 
			
		||||
                .padding(.vertical, 4)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // MARK: - Выход
 | 
			
		||||
            Section {
 | 
			
		||||
                Button(action: {
 | 
			
		||||
                    viewModel.logoutCurrentUser()
 | 
			
		||||
                }) {
 | 
			
		||||
                    HStack {
 | 
			
		||||
                        Image(systemName: "arrow.backward.square")
 | 
			
		||||
                        Text("Выйти из аккаунта")
 | 
			
		||||
                    }
 | 
			
		||||
                    .foregroundColor(.red)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .navigationTitle("Настройки")
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private func openLanguageSettings() {
 | 
			
		||||
        guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
 | 
			
		||||
        UIApplication.shared.open(url)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,259 @@
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
// --- HELPER STRUCTS & EXTENSIONS ---
 | 
			
		||||
 | 
			
		||||
// Dummy data for the account list
 | 
			
		||||
struct Account: Identifiable {
 | 
			
		||||
    let id = UUID()
 | 
			
		||||
    let name: String
 | 
			
		||||
    let username: String
 | 
			
		||||
    let isCurrent: Bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Custom Transition for older Xcode versions
 | 
			
		||||
extension AnyTransition {
 | 
			
		||||
    static var slideAndFade: AnyTransition {
 | 
			
		||||
        let insertion = AnyTransition.move(edge: .top).combined(with: .opacity)
 | 
			
		||||
        let removal = AnyTransition.move(edge: .top).combined(with: .opacity)
 | 
			
		||||
        return .asymmetric(insertion: insertion, removal: removal)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Helper Views for buttons
 | 
			
		||||
struct SideMenuButton: View {
 | 
			
		||||
    let icon: String
 | 
			
		||||
    let title: String
 | 
			
		||||
    let action: () -> Void
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        Button(action: action) {
 | 
			
		||||
            HStack(spacing: 15) {
 | 
			
		||||
                Image(systemName: icon)
 | 
			
		||||
                    .font(.title3)
 | 
			
		||||
                    .frame(width: 30)
 | 
			
		||||
                Text(title)
 | 
			
		||||
                    .font(.subheadline)
 | 
			
		||||
            }
 | 
			
		||||
            .foregroundColor(.primary)
 | 
			
		||||
            .padding(.vertical, 8)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct SideMenuFooterButton: View {
 | 
			
		||||
    let icon: String
 | 
			
		||||
    let title: String
 | 
			
		||||
    let action: () -> Void
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        Button(action: action) {
 | 
			
		||||
            VStack {
 | 
			
		||||
                Image(systemName: icon)
 | 
			
		||||
                    .font(.title2)
 | 
			
		||||
                Text(title)
 | 
			
		||||
                    .font(.caption2)
 | 
			
		||||
            }
 | 
			
		||||
            .foregroundColor(.primary)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// --- MAIN VIEW ---
 | 
			
		||||
 | 
			
		||||
struct SideMenuView: View {
 | 
			
		||||
    @EnvironmentObject var themeManager: ThemeManager
 | 
			
		||||
    @Environment(\.colorScheme) var colorScheme
 | 
			
		||||
    @Binding var isPresented: Bool
 | 
			
		||||
    @State private var isAccountListExpanded = false
 | 
			
		||||
 | 
			
		||||
    // Adjustable paddings
 | 
			
		||||
    private let topPadding: CGFloat = 66
 | 
			
		||||
    private let bottomPadding: CGFloat = 34
 | 
			
		||||
    
 | 
			
		||||
    // Dummy account data
 | 
			
		||||
    private let accounts: [Account] = [
 | 
			
		||||
        Account(name: "Your Name", username: "@yourusername", isCurrent: true),
 | 
			
		||||
        Account(name: "Second Account", username: "@second", isCurrent: false),
 | 
			
		||||
        Account(name: "Another One", username: "@another", isCurrent: false),
 | 
			
		||||
        Account(name: "Test User", username: "@test", isCurrent: false),
 | 
			
		||||
        Account(name: "Creative Profile", username: "@creative", isCurrent: false)
 | 
			
		||||
    ]
 | 
			
		||||
    
 | 
			
		||||
    private var themeToggleButton: some View {
 | 
			
		||||
        Button(action: {
 | 
			
		||||
            themeManager.toggleTheme(from: colorScheme)
 | 
			
		||||
        }) {
 | 
			
		||||
            Image(systemName: iconName)
 | 
			
		||||
                .font(.title2)
 | 
			
		||||
                .foregroundColor(.primary)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var iconName: String {
 | 
			
		||||
        let effectiveScheme: ColorScheme
 | 
			
		||||
        switch themeManager.theme {
 | 
			
		||||
        case .system:
 | 
			
		||||
            effectiveScheme = colorScheme
 | 
			
		||||
        case .light:
 | 
			
		||||
            effectiveScheme = .light
 | 
			
		||||
        case .dark:
 | 
			
		||||
            effectiveScheme = .dark
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return effectiveScheme == .dark ? "sun.max.fill" : "moon.fill"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack(alignment: .leading, spacing: 0) {
 | 
			
		||||
            ScrollView {
 | 
			
		||||
                VStack(alignment: .leading, spacing: 0) { // Parent VStack
 | 
			
		||||
                    
 | 
			
		||||
                    // --- Header ---
 | 
			
		||||
                    HStack(alignment: .top) {
 | 
			
		||||
                        Button(action: {  }) {
 | 
			
		||||
                            Image(systemName: "person.circle.fill")
 | 
			
		||||
                                .resizable()
 | 
			
		||||
                                .frame(width: 60, height: 60)
 | 
			
		||||
                                .foregroundColor(.gray)
 | 
			
		||||
                        }
 | 
			
		||||
                        Spacer()
 | 
			
		||||
                        themeToggleButton
 | 
			
		||||
                    }
 | 
			
		||||
                    .padding(.horizontal, 20)
 | 
			
		||||
                    .padding(.top, topPadding)
 | 
			
		||||
                    .padding(.bottom, 10)
 | 
			
		||||
                    
 | 
			
		||||
                    // --- Header Button ---
 | 
			
		||||
                    Button(action: {
 | 
			
		||||
                        withAnimation(.spring()) {
 | 
			
		||||
                            isAccountListExpanded.toggle()
 | 
			
		||||
                        }
 | 
			
		||||
                    }) {
 | 
			
		||||
                        HStack {
 | 
			
		||||
                            VStack(alignment: .leading) {
 | 
			
		||||
                                Text("Your Name")
 | 
			
		||||
                                    .font(.title3).bold()
 | 
			
		||||
                                Text("@yourusername")
 | 
			
		||||
                                    .font(.footnote)
 | 
			
		||||
                            }
 | 
			
		||||
                            .foregroundColor(.primary)
 | 
			
		||||
                            
 | 
			
		||||
                            Spacer()
 | 
			
		||||
                            
 | 
			
		||||
                            Image(systemName: isAccountListExpanded ? "chevron.up" : "chevron.down")
 | 
			
		||||
                                .font(.headline)
 | 
			
		||||
                                .foregroundColor(.secondary)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    .padding(.horizontal, 20)
 | 
			
		||||
                    .padding(.bottom, 10)
 | 
			
		||||
 | 
			
		||||
                    // --- Collapsible Account List in a clipped container ---
 | 
			
		||||
                    VStack {
 | 
			
		||||
                        if isAccountListExpanded {
 | 
			
		||||
                            VStack(alignment: .leading, spacing: 15) {
 | 
			
		||||
                                ForEach(accounts) { account in
 | 
			
		||||
                                    HStack {
 | 
			
		||||
                                        Button(action: {  }) {
 | 
			
		||||
                                            ZStack {
 | 
			
		||||
                                                Image(systemName: "person.circle.fill")
 | 
			
		||||
                                                    .resizable()
 | 
			
		||||
                                                    .frame(width: 32, height: 32) // Smaller icon
 | 
			
		||||
                                                    .foregroundColor(.secondary)
 | 
			
		||||
                                                
 | 
			
		||||
                                                if account.isCurrent {
 | 
			
		||||
                                                    Image(systemName: "checkmark.circle.fill")
 | 
			
		||||
                                                        .foregroundColor(.blue)
 | 
			
		||||
                                                        .background(Circle().fill(Color(UIColor.systemBackground)))
 | 
			
		||||
                                                        .font(.body) // Smaller checkmark
 | 
			
		||||
                                                        .offset(x: 11, y: 11) // Adjusted offset
 | 
			
		||||
                                                }
 | 
			
		||||
                                            }
 | 
			
		||||
                                            
 | 
			
		||||
                                            VStack(alignment: .leading) {
 | 
			
		||||
                                                Text(account.name).font(.footnote).bold() // Smaller text
 | 
			
		||||
                                                Text(account.username).font(.caption2)      // Smaller text
 | 
			
		||||
                                            }
 | 
			
		||||
                                            .foregroundColor(.primary)
 | 
			
		||||
                                        }
 | 
			
		||||
                                                                            
 | 
			
		||||
                                        Spacer()
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            .padding(.horizontal, 20)
 | 
			
		||||
                            .padding(.vertical, 10)
 | 
			
		||||
                            .transition(.slideAndFade)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    .clipped()
 | 
			
		||||
                    
 | 
			
		||||
                    // Menu Items
 | 
			
		||||
                    VStack(alignment: .leading, spacing: 20) {
 | 
			
		||||
                        // Section 1
 | 
			
		||||
                        VStack(alignment: .leading, spacing: 7) {
 | 
			
		||||
                            SideMenuButton(icon: "person.2.fill", title: "People You May Like", action: {})
 | 
			
		||||
                            SideMenuButton(icon: "star.fill", title: "Fun Fest", action: {})
 | 
			
		||||
                            SideMenuButton(icon: "lightbulb.fill", title: "Creator Center", action: {})
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        Divider()
 | 
			
		||||
 | 
			
		||||
                        // Section 2
 | 
			
		||||
                        VStack(alignment: .leading, spacing: 7) {
 | 
			
		||||
                            Text("CATEGORY").font(.caption2).foregroundColor(.secondary)
 | 
			
		||||
                            SideMenuButton(icon: "doc.text", title: "Drafts", action: {})
 | 
			
		||||
                            SideMenuButton(icon: "bubble.left", title: "My Comments", action: {})
 | 
			
		||||
                            SideMenuButton(icon: "clock", title: "History", action: {})
 | 
			
		||||
                            SideMenuButton(icon: "arrow.down.circle", title: "My Downloads", action: {})
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        Divider()
 | 
			
		||||
 | 
			
		||||
                        // Section 3
 | 
			
		||||
                        VStack(alignment: .leading, spacing: 7) {
 | 
			
		||||
                            Text("SERVICES").font(.caption2).foregroundColor(.secondary)
 | 
			
		||||
                            SideMenuButton(icon: "shippingbox", title: "Orders", action: {})
 | 
			
		||||
                            SideMenuButton(icon: "cart", title: "Cart", action: {})
 | 
			
		||||
                            SideMenuButton(icon: "wallet.pass", title: "Wallet", action: {})
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        Divider()
 | 
			
		||||
                        
 | 
			
		||||
                        // Section 4
 | 
			
		||||
                        VStack(alignment: .leading, spacing: 15) {
 | 
			
		||||
                            SideMenuButton(icon: "square.grid.2x2", title: "Applets", action: {})
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    .padding()
 | 
			
		||||
                }
 | 
			
		||||
                .frame(maxWidth: .infinity, alignment: .leading) // Align to the left
 | 
			
		||||
            }
 | 
			
		||||
            .clipped()
 | 
			
		||||
 | 
			
		||||
            Spacer()
 | 
			
		||||
 | 
			
		||||
            // Footer
 | 
			
		||||
            HStack(spacing: 20) {
 | 
			
		||||
                Spacer()
 | 
			
		||||
                SideMenuFooterButton(icon: "qrcode.viewfinder", title: "Scan", action: {})
 | 
			
		||||
                SideMenuFooterButton(icon: "questionmark.circle", title: "Help Center", action: {})
 | 
			
		||||
                SideMenuFooterButton(icon: "gear", title: "Settings", action: {})
 | 
			
		||||
                Spacer()
 | 
			
		||||
            }
 | 
			
		||||
            .padding()
 | 
			
		||||
            .padding(.bottom, bottomPadding)
 | 
			
		||||
        }
 | 
			
		||||
        .background(Color(UIColor.systemBackground))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- PREVIEW ---
 | 
			
		||||
 | 
			
		||||
struct SideMenuView_Previews: PreviewProvider {
 | 
			
		||||
    static var previews: some View {
 | 
			
		||||
        SideMenuView(isPresented: .constant(true))
 | 
			
		||||
            .environmentObject(ThemeManager())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -18,8 +18,7 @@ struct yobbleApp: App {
 | 
			
		||||
                if viewModel.isLoading {
 | 
			
		||||
                    SplashScreenView()
 | 
			
		||||
                } else if viewModel.isLoggedIn {
 | 
			
		||||
//                    MainView(viewModel: viewModel)
 | 
			
		||||
                    LoginView(viewModel: viewModel)
 | 
			
		||||
                    MainView(viewModel: viewModel)
 | 
			
		||||
                } else {
 | 
			
		||||
                    LoginView(viewModel: viewModel)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user