Compare commits
4 Commits
5d74af5597
...
413e276abe
| Author | SHA1 | Date | |
|---|---|---|---|
| 413e276abe | |||
| 47746690d6 | |||
| 020cd8893a | |||
| 102826ac8a |
@ -187,7 +187,7 @@
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 1A6D61C72E7CD03E00B9F736 /* Build configuration list for PBXProject "yobble" */;
|
||||
developmentRegion = en;
|
||||
developmentRegion = ru;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
|
||||
@ -7,7 +7,8 @@ struct TopBarView: View {
|
||||
@Binding var selectedAccount: String
|
||||
// @Binding var sheetType: ProfileTab.SheetType?
|
||||
var accounts: [String]
|
||||
var viewModel: LoginViewModel
|
||||
// var viewModel: LoginViewModel
|
||||
@ObservedObject var viewModel: LoginViewModel
|
||||
|
||||
// Привязка для управления боковым меню
|
||||
@Binding var isSideMenuPresented: Bool
|
||||
@ -45,7 +46,7 @@ struct TopBarView: View {
|
||||
Spacer()
|
||||
Button(action: { }) {
|
||||
HStack(spacing: 4) {
|
||||
Text(selectedAccount)
|
||||
Text("@\(viewModel.username)")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
Image(systemName: "chevron.down")
|
||||
|
||||
@ -104,7 +104,6 @@ class AuthService {
|
||||
// Сохраняем токены в Keychain
|
||||
KeychainService.shared.save(loginResponse.access_token, forKey: "access_token", service: username)
|
||||
KeychainService.shared.save(loginResponse.refresh_token, forKey: "refresh_token", service: username)
|
||||
print("loginResponse.user_id \(loginResponse.user_id)")
|
||||
KeychainService.shared.save(loginResponse.user_id, forKey: "userId", service: username)
|
||||
UserDefaults.standard.set(username, forKey: "currentUser")
|
||||
|
||||
|
||||
@ -1,45 +1,56 @@
|
||||
{
|
||||
"sourceLanguage" : "en",
|
||||
"sourceLanguage" : "ru",
|
||||
"strings" : {
|
||||
"@yourusername" : {
|
||||
|
||||
},
|
||||
"🌍" : {
|
||||
|
||||
},
|
||||
"CATEGORY" : {
|
||||
|
||||
},
|
||||
"Hello, world!" : {
|
||||
|
||||
},
|
||||
"loading_placeholder" : {
|
||||
|
||||
},
|
||||
"LoginView_button_login" : {
|
||||
"extractionState" : "stale",
|
||||
"@%@" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ебите меня четверо"
|
||||
"value" : "@%@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ok" : {
|
||||
"extractionState" : "stale",
|
||||
"🌍" : {
|
||||
"localizations" : {
|
||||
"ru" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "55454545"
|
||||
"value" : "🌍"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Fun Fest" : {
|
||||
"comment" : "Fun Fest",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Fun Fest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Hello, world!" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Hello, world!"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"OK" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_down_text_1" : {
|
||||
|
||||
@ -52,15 +63,16 @@
|
||||
},
|
||||
"Push-уведомления" : {
|
||||
|
||||
},
|
||||
"SERVICES" : {
|
||||
|
||||
},
|
||||
"Yobble" : {
|
||||
|
||||
},
|
||||
"Your Name" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Yobble"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Активные сессии" : {
|
||||
|
||||
@ -69,16 +81,37 @@
|
||||
|
||||
},
|
||||
"Войти" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Log in"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Выйти из аккаунта" : {
|
||||
|
||||
},
|
||||
"Где найти сохранённые черновики?" : {
|
||||
"comment" : "FAQ question: drafts"
|
||||
},
|
||||
"Данные" : {
|
||||
|
||||
},
|
||||
"Двухфакторная аутентификация" : {
|
||||
|
||||
},
|
||||
"Добавить друзей" : {
|
||||
"comment" : "Add friends",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Add friends"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Другое" : {
|
||||
|
||||
@ -104,14 +137,48 @@
|
||||
"Заглушка: Хранилище данных" : {
|
||||
|
||||
},
|
||||
"Заглушка: Частые вопросы" : {
|
||||
|
||||
"Загрузка..." : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Loading..."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Заказы" : {
|
||||
"comment" : "Orders",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Orders"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Закрыть" : {
|
||||
"comment" : "Закрыть"
|
||||
"comment" : "Закрыть",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Close"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Зарегистрироваться" : {
|
||||
"comment" : "Зарегистрироваться"
|
||||
"comment" : "Зарегистрироваться",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Register"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Здесь будут чаты" : {
|
||||
|
||||
@ -120,22 +187,135 @@
|
||||
|
||||
},
|
||||
"Инвайт-код (необязательно)" : {
|
||||
"comment" : "Инвайт-код"
|
||||
"comment" : "Инвайт-код",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Invite code (optional)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"История" : {
|
||||
"comment" : "History",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "History"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Как сбросить пароль?" : {
|
||||
"comment" : "FAQ question: reset password"
|
||||
},
|
||||
"Как связаться с поддержкой?" : {
|
||||
"comment" : "FAQ question: support"
|
||||
},
|
||||
"Корзина" : {
|
||||
"comment" : "Cart",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Cart"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Кошелёк" : {
|
||||
"comment" : "Wallet",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Wallet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Лента" : {
|
||||
|
||||
},
|
||||
"Лицо" : {
|
||||
|
||||
},
|
||||
"Логин" : {
|
||||
"comment" : "Логин"
|
||||
"comment" : "Логин",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Login"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Логин должен быть от 3 до 32 символов (английские буквы, цифры, _)" : {
|
||||
"comment" : "Логин должен быть от 3 до 32 символов (английские буквы, цифры, _)"
|
||||
"comment" : "Логин должен быть от 3 до 32 символов (английские буквы, цифры, _)",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Username must be 3 to 32 characters (letters, digits, or _)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Логин уже занят." : {
|
||||
|
||||
},
|
||||
"Мини-приложения" : {
|
||||
"comment" : "Applets",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Applets"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Мои загрузки" : {
|
||||
"comment" : "My Downloads",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "My Downloads"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Мои комментарии" : {
|
||||
"comment" : "My Comments",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "My Comments"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Мой профиль" : {
|
||||
|
||||
},
|
||||
"Напишите нам через форму обратной связи в разделе \"Поддержка\"." : {
|
||||
"comment" : "FAQ answer: support"
|
||||
},
|
||||
"Настройки" : {
|
||||
|
||||
"comment" : "Settings",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Не удалось обработать ответ сервера." : {
|
||||
|
||||
@ -206,12 +386,29 @@
|
||||
"Пароль должен быть от 8 до 128 символов" : {
|
||||
"comment" : "Пароль должен быть от 6 до 32 символов"
|
||||
},
|
||||
"Перейдите в раздел \"Настройки > Сменить пароль\" и следуйте инструкциям." : {
|
||||
"comment" : "FAQ answer: reset password"
|
||||
},
|
||||
"Поддержка" : {
|
||||
|
||||
},
|
||||
"Подтверждение пароля" : {
|
||||
"comment" : "Подтверждение пароля"
|
||||
},
|
||||
"Поиск" : {
|
||||
|
||||
},
|
||||
"Помощь" : {
|
||||
"comment" : "Help Center",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Help Center"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Приглашение достигло лимита использования." : {
|
||||
|
||||
},
|
||||
@ -256,6 +453,17 @@
|
||||
},
|
||||
"Сервер не отвечает. Попробуйте позже." : {
|
||||
|
||||
},
|
||||
"Скан" : {
|
||||
"comment" : "Scan",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Scan"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Слишком много запросов." : {
|
||||
|
||||
@ -268,9 +476,37 @@
|
||||
},
|
||||
"Уведомления" : {
|
||||
|
||||
},
|
||||
"Центр авторов" : {
|
||||
"comment" : "Creator Center",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Creator Center"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Частые вопросы" : {
|
||||
"comment" : "FAQ navigation title"
|
||||
},
|
||||
"Чаты" : {
|
||||
|
||||
},
|
||||
"Черновики" : {
|
||||
"comment" : "Drafts",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Drafts"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Черновики доступны в боковом меню в разделе Drafts." : {
|
||||
"comment" : "FAQ answer: drafts"
|
||||
},
|
||||
"Язык" : {
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ class LoginViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
init() {
|
||||
loadStoredUser()
|
||||
// loadStoredUser()
|
||||
|
||||
// Запускаем автологин
|
||||
autoLogin()
|
||||
@ -113,6 +113,6 @@ class LoginViewModel: ObservableObject {
|
||||
username = defaults.string(forKey: DefaultsKeys.currentUser) ?? ""
|
||||
userId = KeychainService.shared.get(forKey: DefaultsKeys.userId, service: username) ?? ""
|
||||
|
||||
print("username: \(username) | userId: \(userId)")
|
||||
if AppConfig.DEBUG{ print("username: \(username) | userId: \(userId)")}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,12 @@ struct LoginView: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
@State private var isShowingRegistration = false
|
||||
@FocusState private var focusedField: Field?
|
||||
|
||||
private enum Field: Hashable {
|
||||
case username
|
||||
case password
|
||||
}
|
||||
|
||||
private var isUsernameValid: Bool {
|
||||
let pattern = "^[A-Za-z0-9_]{3,32}$"
|
||||
@ -29,7 +35,7 @@ struct LoginView: View {
|
||||
Color.clear // чтобы поймать тап
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
hideKeyboard()
|
||||
focusedField = nil
|
||||
}
|
||||
|
||||
VStack {
|
||||
@ -46,8 +52,8 @@ struct LoginView: View {
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
hideKeyboard()
|
||||
}
|
||||
focusedField = nil
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
@ -57,6 +63,7 @@ struct LoginView: View {
|
||||
.cornerRadius(8)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.focused($focusedField, equals: .username)
|
||||
.onChange(of: viewModel.username) { newValue in
|
||||
if newValue.count > 32 {
|
||||
viewModel.username = String(newValue.prefix(32))
|
||||
@ -76,6 +83,7 @@ struct LoginView: View {
|
||||
.background(Color(.secondarySystemBackground))
|
||||
.cornerRadius(8)
|
||||
.autocapitalization(.none)
|
||||
.focused($focusedField, equals: .password)
|
||||
.onChange(of: viewModel.password) { newValue in
|
||||
if newValue.count > 32 {
|
||||
viewModel.password = String(newValue.prefix(32))
|
||||
@ -140,8 +148,8 @@ struct LoginView: View {
|
||||
)
|
||||
}
|
||||
.onTapGesture {
|
||||
hideKeyboard()
|
||||
}
|
||||
focusedField = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,10 +177,6 @@ struct LoginView: View {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
|
||||
private func hideKeyboard() {
|
||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct LoginView_Previews: PreviewProvider {
|
||||
|
||||
@ -22,6 +22,15 @@ struct RegistrationView: View {
|
||||
@State private var showError: Bool = false
|
||||
@State private var errorMessage: String = ""
|
||||
|
||||
@FocusState private var focusedField: Field?
|
||||
|
||||
private enum Field: Hashable {
|
||||
case username
|
||||
case password
|
||||
case confirmPassword
|
||||
case invite
|
||||
}
|
||||
|
||||
private var isUsernameValid: Bool {
|
||||
let pattern = "^[A-Za-z0-9_]{3,32}$"
|
||||
return username.range(of: pattern, options: .regularExpression) != nil
|
||||
@ -45,7 +54,7 @@ struct RegistrationView: View {
|
||||
ZStack(alignment: .top) {
|
||||
Color.clear
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { hideKeyboard() }
|
||||
.onTapGesture { focusedField = nil }
|
||||
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Group {
|
||||
@ -53,6 +62,7 @@ struct RegistrationView: View {
|
||||
TextField(NSLocalizedString("Логин", comment: "Логин"), text: $username)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.focused($focusedField, equals: .username)
|
||||
Spacer()
|
||||
if !username.isEmpty {
|
||||
Image(systemName: isUsernameValid ? "checkmark.circle" : "xmark.circle")
|
||||
@ -79,6 +89,7 @@ struct RegistrationView: View {
|
||||
HStack {
|
||||
SecureField(NSLocalizedString("Пароль", comment: "Пароль"), text: $password)
|
||||
.autocapitalization(.none)
|
||||
.focused($focusedField, equals: .password)
|
||||
Spacer()
|
||||
if !password.isEmpty {
|
||||
Image(systemName: isPasswordValid ? "checkmark.circle" : "xmark.circle")
|
||||
@ -104,6 +115,7 @@ struct RegistrationView: View {
|
||||
HStack {
|
||||
SecureField(NSLocalizedString("Подтверждение пароля", comment: "Подтверждение пароля"), text: $confirmPassword)
|
||||
.autocapitalization(.none)
|
||||
.focused($focusedField, equals: .confirmPassword)
|
||||
Spacer()
|
||||
if !confirmPassword.isEmpty {
|
||||
Image(systemName: isConfirmPasswordValid ? "checkmark.circle" : "xmark.circle")
|
||||
@ -132,6 +144,7 @@ struct RegistrationView: View {
|
||||
.cornerRadius(8)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.focused($focusedField, equals: .invite)
|
||||
}
|
||||
|
||||
Button(action: registerUser) {
|
||||
@ -189,14 +202,10 @@ struct RegistrationView: View {
|
||||
}
|
||||
|
||||
private func dismissSheet() {
|
||||
hideKeyboard()
|
||||
focusedField = nil
|
||||
isPresented = false
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
|
||||
private func hideKeyboard() {
|
||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ struct SplashScreenView: View {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle())
|
||||
.scaleEffect(1.5)
|
||||
Text(NSLocalizedString("loading_placeholder", comment: ""))
|
||||
Text(NSLocalizedString("Загрузка...", comment: ""))
|
||||
.padding(.top, 10)
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,12 +7,12 @@ struct CustomTabBar: View {
|
||||
var body: some View {
|
||||
HStack {
|
||||
// Tab 1: Feed
|
||||
TabBarButton(systemName: "list.bullet.rectangle", text: "Лента", isSelected: selectedTab == 0) {
|
||||
TabBarButton(systemName: "list.bullet.rectangle", text: NSLocalizedString("Лента", comment: ""), isSelected: selectedTab == 0) {
|
||||
selectedTab = 0
|
||||
}
|
||||
|
||||
// Tab 2: Search
|
||||
TabBarButton(systemName: "magnifyingglass", text: "Поиск", isSelected: selectedTab == 1) {
|
||||
TabBarButton(systemName: "magnifyingglass", text: NSLocalizedString("Поиск", comment: ""), isSelected: selectedTab == 1) {
|
||||
selectedTab = 1
|
||||
}
|
||||
|
||||
@ -22,12 +22,12 @@ struct CustomTabBar: View {
|
||||
}
|
||||
|
||||
// Tab 3: Chats
|
||||
TabBarButton(systemName: "bubble.left.and.bubble.right.fill", text: "Чаты", isSelected: selectedTab == 2) {
|
||||
TabBarButton(systemName: "bubble.left.and.bubble.right.fill", text: NSLocalizedString("Чаты", comment: ""), isSelected: selectedTab == 2) {
|
||||
selectedTab = 2
|
||||
}
|
||||
|
||||
// Tab 4: Profile
|
||||
TabBarButton(systemName: "person.crop.square", text: "Лицо", isSelected: selectedTab == 3) {
|
||||
TabBarButton(systemName: "person.crop.square", text: NSLocalizedString("Лицо", comment: ""), isSelected: selectedTab == 3) {
|
||||
selectedTab = 3
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ struct MainView: View {
|
||||
.allowsHitTesting(menuOffset > 0)
|
||||
|
||||
// Боковое меню
|
||||
SideMenuView(isPresented: $isSideMenuPresented)
|
||||
SideMenuView(viewModel: viewModel, isPresented: $isSideMenuPresented)
|
||||
.frame(width: menuWidth)
|
||||
.offset(x: -menuWidth + menuOffset) // Новая логика смещения
|
||||
.ignoresSafeArea(edges: .vertical)
|
||||
|
||||
47
yobble/Views/Tab/Settings/FAQView.swift
Normal file
47
yobble/Views/Tab/Settings/FAQView.swift
Normal file
@ -0,0 +1,47 @@
|
||||
import SwiftUI
|
||||
|
||||
struct FAQView: View {
|
||||
private struct FAQItem: Identifiable {
|
||||
let id = UUID()
|
||||
let question: String
|
||||
let answer: String
|
||||
}
|
||||
|
||||
private let faqItems: [FAQItem] = [
|
||||
FAQItem(
|
||||
question: NSLocalizedString("Как сбросить пароль?", comment: "FAQ question: reset password"),
|
||||
answer: NSLocalizedString("Перейдите в раздел \"Настройки > Сменить пароль\" и следуйте инструкциям.", comment: "FAQ answer: reset password")
|
||||
),
|
||||
FAQItem(
|
||||
question: NSLocalizedString("Где найти сохранённые черновики?", comment: "FAQ question: drafts"),
|
||||
answer: NSLocalizedString("Черновики доступны в боковом меню в разделе Drafts.", comment: "FAQ answer: drafts")
|
||||
),
|
||||
FAQItem(
|
||||
question: NSLocalizedString("Как связаться с поддержкой?", comment: "FAQ question: support"),
|
||||
answer: NSLocalizedString("Напишите нам через форму обратной связи в разделе \"Поддержка\".", comment: "FAQ answer: support")
|
||||
)
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
List(faqItems) { item in
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text(item.question)
|
||||
.font(.headline)
|
||||
Text(item.answer)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding(.vertical, 6)
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.navigationTitle(NSLocalizedString("Частые вопросы", comment: "FAQ navigation title"))
|
||||
}
|
||||
}
|
||||
|
||||
struct FAQView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
FAQView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -54,7 +54,7 @@ struct SettingsView: View {
|
||||
|
||||
// MARK: - Поддержка
|
||||
Section(header: Text("Поддержка")) {
|
||||
NavigationLink(destination: Text("Заглушка: Частые вопросы")) {
|
||||
NavigationLink(destination: FAQView()) {
|
||||
Label("Частые вопросы", systemImage: "questionmark.circle")
|
||||
}
|
||||
NavigationLink(destination: Text("Заглушка: Обратная связь")) {
|
||||
|
||||
@ -53,8 +53,10 @@ struct SideMenuFooterButton: View {
|
||||
Text(title)
|
||||
.font(.caption2)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,24 +64,18 @@ struct SideMenuFooterButton: View {
|
||||
// --- MAIN VIEW ---
|
||||
|
||||
struct SideMenuView: View {
|
||||
@ObservedObject var viewModel: LoginViewModel
|
||||
@EnvironmentObject var themeManager: ThemeManager
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Binding var isPresented: Bool
|
||||
@State private var isAccountListExpanded = false
|
||||
@State private var navigateToSettings = false
|
||||
@State private var navigateToFAQ = 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)
|
||||
@ -106,124 +102,58 @@ struct SideMenuView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
NavigationLink(
|
||||
destination: SettingsView(viewModel: viewModel),
|
||||
isActive: $navigateToSettings
|
||||
) {
|
||||
EmptyView()
|
||||
}
|
||||
NavigationLink(
|
||||
destination: FAQView(),
|
||||
isActive: $navigateToFAQ
|
||||
) {
|
||||
EmptyView()
|
||||
}
|
||||
|
||||
ScrollView(showsIndicators: false) {
|
||||
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: {})
|
||||
SideMenuButton(icon: "person.2.fill", title: NSLocalizedString("Добавить друзей", comment: "Add friends"), action: {})
|
||||
SideMenuButton(icon: "star.fill", title: NSLocalizedString("Fun Fest", comment: "Fun Fest"), action: {})
|
||||
SideMenuButton(icon: "lightbulb.fill", title: NSLocalizedString("Центр авторов", comment: "Creator Center"), action: {})
|
||||
}
|
||||
.padding(.top, topPadding)
|
||||
|
||||
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: {})
|
||||
// Text("CATEGORY").font(.caption2).foregroundColor(.secondary)
|
||||
SideMenuButton(icon: "doc.text", title: NSLocalizedString("Черновики", comment: "Drafts"), action: {})
|
||||
SideMenuButton(icon: "bubble.left", title: NSLocalizedString("Мои комментарии", comment: "My Comments"), action: {})
|
||||
SideMenuButton(icon: "clock", title: NSLocalizedString("История", comment: "History"), action: {})
|
||||
SideMenuButton(icon: "arrow.down.circle", title: NSLocalizedString("Мои загрузки", comment: "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: {})
|
||||
// Text("SERVICES").font(.caption2).foregroundColor(.secondary)
|
||||
SideMenuButton(icon: "shippingbox", title: NSLocalizedString("Заказы", comment: "Orders"), action: {})
|
||||
SideMenuButton(icon: "cart", title: NSLocalizedString("Корзина", comment: "Cart"), action: {})
|
||||
SideMenuButton(icon: "wallet.pass", title: NSLocalizedString("Кошелёк", comment: "Wallet"), action: {})
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
// Section 4
|
||||
VStack(alignment: .leading, spacing: 15) {
|
||||
SideMenuButton(icon: "square.grid.2x2", title: "Applets", action: {})
|
||||
SideMenuButton(icon: "square.grid.2x2", title: NSLocalizedString("Мини-приложения", comment: "Applets"), action: {})
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
@ -235,14 +165,35 @@ struct SideMenuView: View {
|
||||
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()
|
||||
HStack(spacing: 0) {
|
||||
SideMenuFooterButton(
|
||||
icon: "qrcode.viewfinder",
|
||||
title: NSLocalizedString("Скан", comment: "Scan"),
|
||||
action: {}
|
||||
)
|
||||
Spacer(minLength: 40)
|
||||
SideMenuFooterButton(
|
||||
icon: "questionmark.circle",
|
||||
title: NSLocalizedString("Помощь", comment: "Help Center")
|
||||
) {
|
||||
withAnimation(.easeInOut) {
|
||||
isPresented = false
|
||||
}
|
||||
navigateToFAQ = true
|
||||
}
|
||||
Spacer(minLength: 40)
|
||||
SideMenuFooterButton(
|
||||
icon: "gear",
|
||||
title: NSLocalizedString("Настройки", comment: "Settings")
|
||||
) {
|
||||
withAnimation(.easeInOut) {
|
||||
isPresented = false
|
||||
}
|
||||
navigateToSettings = true
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.padding(.horizontal, 28)
|
||||
.padding(.vertical)
|
||||
.padding(.bottom, bottomPadding)
|
||||
}
|
||||
.background(Color(UIColor.systemBackground))
|
||||
@ -253,7 +204,10 @@ struct SideMenuView: View {
|
||||
|
||||
struct SideMenuView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SideMenuView(isPresented: .constant(true))
|
||||
.environmentObject(ThemeManager())
|
||||
let mockViewModel = LoginViewModel()
|
||||
NavigationView {
|
||||
SideMenuView(viewModel: mockViewModel, isPresented: .constant(true))
|
||||
.environmentObject(ThemeManager())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user